activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,11 @@
1
+ module ActiveRecord::Associations
2
+ module ForeignAssociation
3
+ def foreign_key_present?
4
+ if reflection.klass.primary_key
5
+ owner.attribute_present?(reflection.active_record_primary_key)
6
+ else
7
+ false
8
+ end
9
+ end
10
+ end
11
+ end
@@ -6,9 +6,34 @@ module ActiveRecord
6
6
  # If the association has a <tt>:through</tt> option further specialization
7
7
  # is provided by its child HasManyThroughAssociation.
8
8
  class HasManyAssociation < CollectionAssociation #:nodoc:
9
+ include ForeignAssociation
10
+
11
+ def handle_dependency
12
+ case options[:dependent]
13
+ when :restrict_with_exception
14
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
15
+
16
+ when :restrict_with_error
17
+ unless empty?
18
+ record = klass.human_attribute_name(reflection.name).downcase
19
+ owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
20
+ false
21
+ end
22
+
23
+ else
24
+ if options[:dependent] == :destroy
25
+ # No point in executing the counter update since we're going to destroy the parent anyway
26
+ load_target.each { |t| t.destroyed_by_association = reflection }
27
+ destroy_all
28
+ else
29
+ delete_all
30
+ end
31
+ end
32
+ end
9
33
 
10
34
  def insert_record(record, validate = true, raise = false)
11
35
  set_owner_attributes(record)
36
+ set_inverse_instance(record)
12
37
 
13
38
  if raise
14
39
  record.save!(:validate => validate)
@@ -17,13 +42,21 @@ module ActiveRecord
17
42
  end
18
43
  end
19
44
 
45
+ def empty?
46
+ if has_cached_counter?
47
+ size.zero?
48
+ else
49
+ super
50
+ end
51
+ end
52
+
20
53
  private
21
54
 
22
55
  # Returns the number of records in this collection.
23
56
  #
24
57
  # If the association has a counter cache it gets that value. Otherwise
25
58
  # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
26
- # there's one. Some configuration options like :group make it impossible
59
+ # there's one. Some configuration options like :group make it impossible
27
60
  # to do an SQL count, in those cases the array count will be used.
28
61
  #
29
62
  # That does not depend on whether the collection has already been loaded
@@ -34,11 +67,9 @@ module ActiveRecord
34
67
  # the loaded flag is set to true as well.
35
68
  def count_records
36
69
  count = if has_cached_counter?
37
- owner.send(:read_attribute, cached_counter_attribute_name)
38
- elsif options[:counter_sql] || options[:finder_sql]
39
- reflection.klass.count_by_sql(custom_counter_sql)
70
+ owner._read_attribute cached_counter_attribute_name
40
71
  else
41
- scoped.count
72
+ scope.count
42
73
  end
43
74
 
44
75
  # If there's nothing in the database and @target has no new records
@@ -46,23 +77,45 @@ module ActiveRecord
46
77
  # documented side-effect of the method that may avoid an extra SELECT.
47
78
  @target ||= [] and loaded! if count == 0
48
79
 
49
- [options[:limit], count].compact.min
80
+ [association_scope.limit_value, count].compact.min
50
81
  end
51
82
 
52
- def has_cached_counter?(reflection = reflection)
53
- owner.attribute_present?(cached_counter_attribute_name(reflection))
83
+
84
+ # Returns whether a counter cache should be used for this association.
85
+ #
86
+ # The counter_cache option must be given on either the owner or inverse
87
+ # association, and the column must be present on the owner.
88
+ def has_cached_counter?(reflection = reflection())
89
+ if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
90
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
91
+ end
54
92
  end
55
93
 
56
- def cached_counter_attribute_name(reflection = reflection)
57
- "#{reflection.name}_count"
94
+ def cached_counter_attribute_name(reflection = reflection())
95
+ if reflection.options[:counter_cache]
96
+ reflection.options[:counter_cache].to_s
97
+ else
98
+ "#{reflection.name}_count"
99
+ end
58
100
  end
59
101
 
60
- def update_counter(difference, reflection = reflection)
102
+ def update_counter(difference, reflection = reflection())
103
+ update_counter_in_database(difference, reflection)
104
+ update_counter_in_memory(difference, reflection)
105
+ end
106
+
107
+ def update_counter_in_database(difference, reflection = reflection())
61
108
  if has_cached_counter?(reflection)
62
109
  counter = cached_counter_attribute_name(reflection)
63
110
  owner.class.update_counters(owner.id, counter => difference)
111
+ end
112
+ end
113
+
114
+ def update_counter_in_memory(difference, reflection = reflection())
115
+ if counter_must_be_updated_by_has_many?(reflection)
116
+ counter = cached_counter_attribute_name(reflection)
64
117
  owner[counter] += difference
65
- owner.changed_attributes.delete(counter) # eww
118
+ owner.send(:clear_attribute_changes, counter) # eww
66
119
  end
67
120
  end
68
121
 
@@ -76,28 +129,70 @@ module ActiveRecord
76
129
  # it will be decremented twice.
77
130
  #
78
131
  # Hence this method.
79
- def inverse_updates_counter_cache?(reflection = reflection)
132
+ def inverse_which_updates_counter_cache(reflection = reflection())
80
133
  counter_name = cached_counter_attribute_name(reflection)
81
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
134
+ inverse_which_updates_counter_named(counter_name, reflection)
135
+ end
136
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
137
+
138
+ def inverse_which_updates_counter_named(counter_name, reflection)
139
+ reflection.klass._reflections.values.find { |inverse_reflection|
140
+ inverse_reflection.belongs_to? &&
82
141
  inverse_reflection.counter_cache_column == counter_name
83
142
  }
84
143
  end
144
+ alias inverse_updates_counter_named? inverse_which_updates_counter_named
145
+
146
+ def inverse_updates_counter_in_memory?(reflection)
147
+ inverse = inverse_which_updates_counter_cache(reflection)
148
+ inverse && inverse == reflection.inverse_of
149
+ end
150
+
151
+ def counter_must_be_updated_by_has_many?(reflection)
152
+ !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
153
+ end
154
+
155
+ def delete_count(method, scope)
156
+ if method == :delete_all
157
+ scope.delete_all
158
+ else
159
+ scope.update_all(reflection.foreign_key => nil)
160
+ end
161
+ end
162
+
163
+ def delete_or_nullify_all_records(method)
164
+ count = delete_count(method, self.scope)
165
+ update_counter(-count)
166
+ end
85
167
 
86
168
  # Deletes the records according to the <tt>:dependent</tt> option.
87
169
  def delete_records(records, method)
88
170
  if method == :destroy
89
- records.each { |r| r.destroy }
171
+ records.each(&:destroy!)
90
172
  update_counter(-records.length) unless inverse_updates_counter_cache?
91
173
  else
92
- keys = records.map { |r| r[reflection.association_primary_key] }
93
- scope = scoped.where(reflection.association_primary_key => keys)
94
-
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
174
+ scope = self.scope.where(reflection.klass.primary_key => records)
175
+ update_counter(-delete_count(method, scope))
176
+ end
177
+ end
178
+
179
+ def concat_records(records, *)
180
+ update_counter_if_success(super, records.length)
181
+ end
182
+
183
+ def _create_record(attributes, *)
184
+ if attributes.is_a?(Array)
185
+ super
186
+ else
187
+ update_counter_if_success(super, 1)
188
+ end
189
+ end
190
+
191
+ def update_counter_if_success(saved_successfully, difference)
192
+ if saved_successfully
193
+ update_counter_in_memory(difference)
100
194
  end
195
+ saved_successfully
101
196
  end
102
197
  end
103
198
  end
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/string/filters'
2
2
 
3
3
  module ActiveRecord
4
4
  # = Active Record Has Many Through Association
@@ -8,34 +8,50 @@ module ActiveRecord
8
8
 
9
9
  def initialize(owner, reflection)
10
10
  super
11
- @through_records = {}
11
+
12
+ @through_records = {}
13
+ @through_association = nil
12
14
  end
13
15
 
14
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
15
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
16
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
17
- # SELECT query if you use #length.
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.
18
21
  def size
19
22
  if has_cached_counter?
20
- owner.send(:read_attribute, cached_counter_attribute_name)
23
+ owner._read_attribute cached_counter_attribute_name(reflection)
21
24
  elsif loaded?
22
25
  target.size
23
26
  else
24
- count
27
+ super
25
28
  end
26
29
  end
27
30
 
28
31
  def concat(*records)
29
32
  unless owner.new_record?
30
33
  records.flatten.each do |record|
31
- raise_on_type_mismatch(record)
32
- record.save! if record.new_record?
34
+ raise_on_type_mismatch!(record)
33
35
  end
34
36
  end
35
37
 
36
38
  super
37
39
  end
38
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
+
39
55
  def insert_record(record, validate = true, raise = false)
40
56
  ensure_not_nested
41
57
 
@@ -48,51 +64,71 @@ module ActiveRecord
48
64
  end
49
65
 
50
66
  save_through_record(record)
51
- update_counter(1)
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
52
77
  record
53
78
  end
54
79
 
55
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
56
- # SQL-only implementation.
57
- alias delete_all_on_destroy delete_all
58
-
59
80
  private
60
81
 
61
82
  def through_association
62
- owner.association(through_reflection.name)
83
+ @through_association ||= owner.association(through_reflection.name)
63
84
  end
64
85
 
65
- # We temporarily cache through record that has been build, because if we build a
66
- # through record in build_record and then subsequently call insert_record, then we
67
- # want to use the exact same object.
86
+ # The through record (built with build_record) is temporarily cached
87
+ # so that it may be reused if insert_record is subsequently called.
68
88
  #
69
- # However, after insert_record has been called, we clear the cache entry because
70
- # we want it to be possible to have multiple instances of the same record in an
71
- # association
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.
72
91
  def build_through_record(record)
73
92
  @through_records[record.object_id] ||= begin
74
- through_record = through_association.build(construct_join_attributes(record))
93
+ ensure_mutable
94
+
95
+ through_record = through_association.build(*options_for_through_record)
75
96
  through_record.send("#{source_reflection.name}=", record)
97
+
98
+ if options[:source_type]
99
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
100
+ end
101
+
76
102
  through_record
77
103
  end
78
104
  end
79
105
 
106
+ def options_for_through_record
107
+ [through_scope_attributes]
108
+ end
109
+
110
+ def through_scope_attributes
111
+ scope.where_values_hash(through_association.reflection.name.to_s).
112
+ except!(through_association.reflection.foreign_key,
113
+ through_association.reflection.klass.inheritance_column)
114
+ end
115
+
80
116
  def save_through_record(record)
81
117
  build_through_record(record).save!
82
118
  ensure
83
119
  @through_records.delete(record.object_id)
84
120
  end
85
121
 
86
- def build_record(attributes, options = {})
122
+ def build_record(attributes)
87
123
  ensure_not_nested
88
124
 
89
- record = super(attributes, options)
125
+ record = super(attributes)
90
126
 
91
127
  inverse = source_reflection.inverse_of
92
128
  if inverse
93
- if inverse.macro == :has_many
129
+ if inverse.collection?
94
130
  record.send(inverse.name) << build_through_record(record)
95
- elsif inverse.macro == :has_one
131
+ elsif inverse.has_one?
96
132
  record.send("#{inverse.name}=", build_through_record(record))
97
133
  end
98
134
  end
@@ -101,11 +137,7 @@ module ActiveRecord
101
137
  end
102
138
 
103
139
  def target_reflection_has_associated_record?
104
- if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?
105
- false
106
- else
107
- true
108
- end
140
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
109
141
  end
110
142
 
111
143
  def update_through_counter?(method)
@@ -119,44 +151,73 @@ module ActiveRecord
119
151
  end
120
152
  end
121
153
 
154
+ def delete_or_nullify_all_records(method)
155
+ delete_records(load_target, method)
156
+ end
157
+
122
158
  def delete_records(records, method)
123
159
  ensure_not_nested
124
160
 
125
- through = through_association
126
- scope = through.scoped.where(construct_join_attributes(*records))
161
+ scope = through_association.scope
162
+ scope.where! construct_join_attributes(*records)
127
163
 
128
164
  case method
129
165
  when :destroy
130
- count = scope.destroy_all.length
166
+ if scope.klass.primary_key
167
+ count = scope.destroy_all.length
168
+ else
169
+ scope.each do |record|
170
+ record._run_destroy_callbacks
171
+ end
172
+
173
+ arel = scope.arel
174
+
175
+ stmt = Arel::DeleteManager.new arel.engine
176
+ stmt.from scope.klass.arel_table
177
+ stmt.wheres = arel.constraints
178
+
179
+ count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
180
+ end
131
181
  when :nullify
132
182
  count = scope.update_all(source_reflection.foreign_key => nil)
133
183
  else
134
184
  count = scope.delete_all
135
185
  end
136
186
 
137
- delete_through_records(through, records)
187
+ delete_through_records(records)
138
188
 
139
- if through_reflection.macro == :has_many && update_through_counter?(method)
140
- update_counter(-count, through_reflection)
189
+ if source_reflection.options[:counter_cache] && method != :destroy
190
+ counter = source_reflection.counter_cache_column
191
+ klass.decrement_counter counter, records.map(&:id)
141
192
  end
142
193
 
143
- update_counter(-count)
194
+ if through_reflection.collection? && update_through_counter?(method)
195
+ update_counter(-count, through_reflection)
196
+ else
197
+ update_counter(-count)
198
+ end
144
199
  end
145
200
 
146
201
  def through_records_for(record)
147
202
  attributes = construct_join_attributes(record)
148
203
  candidates = Array.wrap(through_association.target)
149
- candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
204
+ candidates.find_all do |c|
205
+ attributes.all? do |key, value|
206
+ c.public_send(key) == value
207
+ end
208
+ end
150
209
  end
151
210
 
152
- def delete_through_records(through, records)
211
+ def delete_through_records(records)
153
212
  records.each do |record|
154
213
  through_records = through_records_for(record)
155
214
 
156
- if through_reflection.macro == :has_many
157
- through_records.each { |r| through.target.delete(r) }
215
+ if through_reflection.collection?
216
+ through_records.each { |r| through_association.target.delete(r) }
158
217
  else
159
- through.target = nil if through_records.include?(through.target)
218
+ if through_records.include?(through_association.target)
219
+ through_association.target = nil
220
+ end
160
221
  end
161
222
 
162
223
  @through_records.delete(record.object_id)
@@ -165,13 +226,22 @@ module ActiveRecord
165
226
 
166
227
  def find_target
167
228
  return [] unless target_reflection_has_associated_record?
168
- scoped.all
229
+ get_records
169
230
  end
170
231
 
171
232
  # NOTE - not sure that we can actually cope with inverses here
172
233
  def invertible_for?(record)
173
234
  false
174
235
  end
236
+
237
+ def has_cached_counter?(reflection = reflection())
238
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
239
+ end
240
+
241
+ def through_reflection_updates_counter_cache?
242
+ counter_name = cached_counter_attribute_name
243
+ inverse_updates_counter_named?(counter_name, through_reflection)
244
+ end
175
245
  end
176
246
  end
177
247
  end
@@ -1,26 +1,48 @@
1
- require 'active_support/core_ext/object/inclusion'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Belongs To Has One Association
5
3
  module Associations
6
4
  class HasOneAssociation < SingularAssociation #:nodoc:
5
+ include ForeignAssociation
6
+
7
+ def handle_dependency
8
+ case options[:dependent]
9
+ when :restrict_with_exception
10
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
11
+
12
+ when :restrict_with_error
13
+ if load_target
14
+ record = klass.human_attribute_name(reflection.name).downcase
15
+ owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
16
+ false
17
+ end
18
+
19
+ else
20
+ delete
21
+ end
22
+ end
23
+
7
24
  def replace(record, save = true)
8
- raise_on_type_mismatch(record) if record
25
+ raise_on_type_mismatch!(record) if record
9
26
  load_target
10
27
 
11
- reflection.klass.transaction do
12
- if target && target != record
13
- remove_target!(options[:dependent]) unless target.destroyed?
14
- end
28
+ return self.target if !(target || record)
29
+
30
+ assigning_another_record = target != record
31
+ if assigning_another_record || record.changed?
32
+ save &&= owner.persisted?
33
+
34
+ transaction_if(save) do
35
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
15
36
 
16
- if record
17
- set_owner_attributes(record)
18
- set_inverse_instance(record)
37
+ if record
38
+ set_owner_attributes(record)
39
+ set_inverse_instance(record)
19
40
 
20
- if owner.persisted? && save && !record.save
21
- nullify_owner_attributes(record)
22
- set_owner_attributes(target) if target
23
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
41
+ if save && !record.save
42
+ nullify_owner_attributes(record)
43
+ set_owner_attributes(target) if target
44
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
45
+ end
24
46
  end
25
47
  end
26
48
  end
@@ -36,7 +58,7 @@ module ActiveRecord
36
58
  when :destroy
37
59
  target.destroy
38
60
  when :nullify
39
- target.update_attribute(reflection.foreign_key, nil)
61
+ target.update_columns(reflection.foreign_key => nil)
40
62
  end
41
63
  end
42
64
  end
@@ -52,22 +74,33 @@ module ActiveRecord
52
74
  end
53
75
 
54
76
  def remove_target!(method)
55
- if method.in?([:delete, :destroy])
56
- target.send(method)
57
- else
58
- nullify_owner_attributes(target)
77
+ case method
78
+ when :delete
79
+ target.delete
80
+ when :destroy
81
+ target.destroy
82
+ else
83
+ nullify_owner_attributes(target)
59
84
 
60
- if target.persisted? && owner.persisted? && !target.save
61
- set_owner_attributes(target)
62
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
63
- "The record failed to save when after its foreign key was set to nil."
64
- end
85
+ if target.persisted? && owner.persisted? && !target.save
86
+ set_owner_attributes(target)
87
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
88
+ "The record failed to save after its foreign key was set to nil."
89
+ end
65
90
  end
66
91
  end
67
92
 
68
93
  def nullify_owner_attributes(record)
69
94
  record[reflection.foreign_key] = nil
70
95
  end
96
+
97
+ def transaction_if(value)
98
+ if value
99
+ reflection.klass.transaction { yield }
100
+ else
101
+ yield
102
+ end
103
+ end
71
104
  end
72
105
  end
73
106
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  attributes = construct_join_attributes(record)
24
24
 
25
25
  if through_record
26
- through_record.update_attributes(attributes)
26
+ through_record.update(attributes)
27
27
  elsif owner.new_record?
28
28
  through_proxy.build(attributes)
29
29
  else