activerecord 4.2.0

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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,184 @@
1
+ module ActiveRecord
2
+ # = Active Record Has Many Association
3
+ module Associations
4
+ # This is the proxy that handles a has many association.
5
+ #
6
+ # If the association has a <tt>:through</tt> option further specialization
7
+ # is provided by its child HasManyThroughAssociation.
8
+ class HasManyAssociation < CollectionAssociation #:nodoc:
9
+
10
+ def handle_dependency
11
+ case options[:dependent]
12
+ when :restrict_with_exception
13
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
14
+
15
+ when :restrict_with_error
16
+ 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
+ end
21
+
22
+ 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
+ end
31
+ end
32
+
33
+ def insert_record(record, validate = true, raise = false)
34
+ 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
42
+ end
43
+
44
+ def empty?
45
+ if has_cached_counter?
46
+ size.zero?
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # Returns the number of records in this collection.
55
+ #
56
+ # If the association has a counter cache it gets that value. Otherwise
57
+ # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
58
+ # there's one. Some configuration options like :group make it impossible
59
+ # to do an SQL count, in those cases the array count will be used.
60
+ #
61
+ # That does not depend on whether the collection has already been loaded
62
+ # or not. The +size+ method is the one that takes the loaded flag into
63
+ # account and delegates to +count_records+ if needed.
64
+ #
65
+ # If the collection is empty the target is set to an empty array and
66
+ # the loaded flag is set to true as well.
67
+ def count_records
68
+ count = if has_cached_counter?
69
+ owner._read_attribute cached_counter_attribute_name
70
+ else
71
+ scope.count
72
+ end
73
+
74
+ # If there's nothing in the database and @target has no new records
75
+ # we are certain the current target is an empty array. This is a
76
+ # documented side-effect of the method that may avoid an extra SELECT.
77
+ @target ||= [] and loaded! if count == 0
78
+
79
+ [association_scope.limit_value, count].compact.min
80
+ end
81
+
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
+ 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)
99
+ end
100
+ end
101
+
102
+ 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
107
+ end
108
+ end
109
+
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
+ def delete_count(method, scope)
133
+ if method == :delete_all
134
+ scope.delete_all
135
+ else
136
+ scope.update_all(reflection.foreign_key => nil)
137
+ end
138
+ end
139
+
140
+ def delete_or_nullify_all_records(method)
141
+ count = delete_count(method, self.scope)
142
+ update_counter(-count)
143
+ end
144
+
145
+ # Deletes the records according to the <tt>:dependent</tt> option.
146
+ def delete_records(records, method)
147
+ if method == :destroy
148
+ records.each(&:destroy!)
149
+ update_counter(-records.length) unless inverse_updates_counter_cache?
150
+ else
151
+ scope = self.scope.where(reflection.klass.primary_key => records)
152
+ update_counter(-delete_count(method, scope))
153
+ end
154
+ end
155
+
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
+ def concat_records(records, *)
165
+ update_counter_if_success(super, records.length)
166
+ end
167
+
168
+ def _create_record(attributes, *)
169
+ if attributes.is_a?(Array)
170
+ super
171
+ else
172
+ update_counter_if_success(super, 1)
173
+ end
174
+ end
175
+
176
+ def update_counter_if_success(saved_successfully, difference)
177
+ if saved_successfully
178
+ update_counter_in_memory(difference)
179
+ end
180
+ saved_successfully
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,238 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Has Many Through Association
5
+ module Associations
6
+ class HasManyThroughAssociation < HasManyAssociation #:nodoc:
7
+ include ThroughAssociation
8
+
9
+ def initialize(owner, reflection)
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
29
+ end
30
+
31
+ def concat(*records)
32
+ unless owner.new_record?
33
+ records.flatten.each do |record|
34
+ raise_on_type_mismatch!(record)
35
+ end
36
+ end
37
+
38
+ super
39
+ end
40
+
41
+ def concat_records(records)
42
+ ensure_not_nested
43
+
44
+ records = super(records, true)
45
+
46
+ if owner.new_record? && records
47
+ records.flatten.each do |record|
48
+ build_through_record(record)
49
+ end
50
+ end
51
+
52
+ records
53
+ end
54
+
55
+ def insert_record(record, validate = true, raise = false)
56
+ ensure_not_nested
57
+
58
+ if record.new_record?
59
+ if raise
60
+ record.save!(:validate => validate)
61
+ else
62
+ return unless record.save(:validate => validate)
63
+ end
64
+ end
65
+
66
+ 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
+
75
+ update_counter_in_database(1)
76
+ end
77
+ record
78
+ end
79
+
80
+ private
81
+
82
+ def through_association
83
+ @through_association ||= owner.association(through_reflection.name)
84
+ end
85
+
86
+ # The through record (built with build_record) is temporarily cached
87
+ # so that it may be reused if insert_record is subsequently called.
88
+ #
89
+ # However, after insert_record has been called, the cache is cleared in
90
+ # order to allow multiple instances of the same record in an association.
91
+ def build_through_record(record)
92
+ @through_records[record.object_id] ||= begin
93
+ ensure_mutable
94
+
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
100
+
101
+ def options_for_through_record
102
+ [through_scope_attributes]
103
+ end
104
+
105
+ def through_scope_attributes
106
+ scope.where_values_hash(through_association.reflection.name.to_s).
107
+ except!(through_association.reflection.foreign_key,
108
+ through_association.reflection.klass.inheritance_column)
109
+ end
110
+
111
+ def save_through_record(record)
112
+ build_through_record(record).save!
113
+ ensure
114
+ @through_records.delete(record.object_id)
115
+ end
116
+
117
+ def build_record(attributes)
118
+ ensure_not_nested
119
+
120
+ record = super(attributes)
121
+
122
+ inverse = source_reflection.inverse_of
123
+ if inverse
124
+ if inverse.collection?
125
+ record.send(inverse.name) << build_through_record(record)
126
+ elsif inverse.has_one?
127
+ record.send("#{inverse.name}=", build_through_record(record))
128
+ end
129
+ end
130
+
131
+ record
132
+ end
133
+
134
+ def target_reflection_has_associated_record?
135
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
136
+ end
137
+
138
+ def update_through_counter?(method)
139
+ case method
140
+ when :destroy
141
+ !inverse_updates_counter_cache?(through_reflection)
142
+ when :nullify
143
+ false
144
+ else
145
+ true
146
+ end
147
+ end
148
+
149
+ def delete_or_nullify_all_records(method)
150
+ delete_records(load_target, method)
151
+ end
152
+
153
+ def delete_records(records, method)
154
+ ensure_not_nested
155
+
156
+ scope = through_association.scope
157
+ scope.where! construct_join_attributes(*records)
158
+
159
+ case method
160
+ when :destroy
161
+ if scope.klass.primary_key
162
+ count = scope.destroy_all.length
163
+ 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)
175
+ end
176
+ when :nullify
177
+ count = scope.update_all(source_reflection.foreign_key => nil)
178
+ else
179
+ count = scope.delete_all
180
+ end
181
+
182
+ delete_through_records(records)
183
+
184
+ if source_reflection.options[:counter_cache] && method != :destroy
185
+ counter = source_reflection.counter_cache_column
186
+ klass.decrement_counter counter, records.map(&:id)
187
+ end
188
+
189
+ if through_reflection.collection? && update_through_counter?(method)
190
+ update_counter(-count, through_reflection)
191
+ end
192
+
193
+ update_counter(-count)
194
+ end
195
+
196
+ def through_records_for(record)
197
+ attributes = construct_join_attributes(record)
198
+ candidates = Array.wrap(through_association.target)
199
+ candidates.find_all do |c|
200
+ attributes.all? do |key, value|
201
+ c.public_send(key) == value
202
+ end
203
+ end
204
+ end
205
+
206
+ def delete_through_records(records)
207
+ records.each do |record|
208
+ through_records = through_records_for(record)
209
+
210
+ if through_reflection.collection?
211
+ through_records.each { |r| through_association.target.delete(r) }
212
+ else
213
+ if through_records.include?(through_association.target)
214
+ through_association.target = nil
215
+ end
216
+ end
217
+
218
+ @through_records.delete(record.object_id)
219
+ end
220
+ end
221
+
222
+ def find_target
223
+ return [] unless target_reflection_has_associated_record?
224
+ get_records
225
+ end
226
+
227
+ # NOTE - not sure that we can actually cope with inverses here
228
+ def invertible_for?(record)
229
+ false
230
+ 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
+ end
237
+ end
238
+ end
@@ -0,0 +1,105 @@
1
+ module ActiveRecord
2
+ # = Active Record Belongs To Has One Association
3
+ module Associations
4
+ class HasOneAssociation < SingularAssociation #:nodoc:
5
+
6
+ def handle_dependency
7
+ case options[:dependent]
8
+ when :restrict_with_exception
9
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
10
+
11
+ when :restrict_with_error
12
+ 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
+ end
17
+
18
+ else
19
+ delete
20
+ end
21
+ end
22
+
23
+ def replace(record, save = true)
24
+ raise_on_type_mismatch!(record) if record
25
+ load_target
26
+
27
+ return self.target if !(target || record)
28
+
29
+ assigning_another_record = target != record
30
+ if assigning_another_record || record.changed?
31
+ save &&= owner.persisted?
32
+
33
+ transaction_if(save) do
34
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
35
+
36
+ if record
37
+ set_owner_attributes(record)
38
+ set_inverse_instance(record)
39
+
40
+ if save && !record.save
41
+ nullify_owner_attributes(record)
42
+ set_owner_attributes(target) if target
43
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ self.target = record
50
+ end
51
+
52
+ def delete(method = options[:dependent])
53
+ if load_target
54
+ 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)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ # The reason that the save param for replace is false, if for create (not just build),
68
+ # is because the setting of the foreign keys is actually handled by the scoping when
69
+ # the record is instantiated, and so they are set straight away and do not need to be
70
+ # updated within replace.
71
+ def set_new_record(record)
72
+ replace(record, false)
73
+ end
74
+
75
+ def remove_target!(method)
76
+ 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
89
+ end
90
+ end
91
+
92
+ def nullify_owner_attributes(record)
93
+ record[reflection.foreign_key] = nil
94
+ end
95
+
96
+ def transaction_if(value)
97
+ if value
98
+ reflection.klass.transaction { yield }
99
+ else
100
+ yield
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end