activerecord 3.2.19 → 5.0.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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  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 +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,123 +1,164 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Counter Cache
3
3
  module CounterCache
4
- # Resets one or more counter caches to their correct value using an SQL
5
- # count query. This is useful when adding new counter caches, or if the
6
- # counter has been corrupted or modified directly by SQL.
7
- #
8
- # ==== Parameters
9
- #
10
- # * +id+ - The id of the object you wish to reset a counter on.
11
- # * +counters+ - One or more counter names to reset
12
- #
13
- # ==== Examples
14
- #
15
- # # For Post with id #1 records reset the comments_count
16
- # Post.reset_counters(1, :comments)
17
- def reset_counters(id, *counters)
18
- object = find(id)
19
- counters.each do |association|
20
- has_many_association = reflect_on_association(association.to_sym)
21
-
22
- if has_many_association.options[:as]
23
- has_many_association.options[:as].to_s.classify
24
- else
25
- self.name
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Resets one or more counter caches to their correct value using an SQL
8
+ # count query. This is useful when adding new counter caches, or if the
9
+ # counter has been corrupted or modified directly by SQL.
10
+ #
11
+ # ==== Parameters
12
+ #
13
+ # * +id+ - The id of the object you wish to reset a counter on.
14
+ # * +counters+ - One or more association counters to reset. Association name or counter name can be given.
15
+ #
16
+ # ==== Examples
17
+ #
18
+ # # For Post with id #1 records reset the comments_count
19
+ # Post.reset_counters(1, :comments)
20
+ def reset_counters(id, *counters)
21
+ object = find(id)
22
+ counters.each do |counter_association|
23
+ has_many_association = _reflect_on_association(counter_association)
24
+ unless has_many_association
25
+ has_many = reflect_on_all_associations(:has_many)
26
+ has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
27
+ counter_association = has_many_association.plural_name if has_many_association
28
+ end
29
+ raise ArgumentError, "'#{self.name}' has no association called '#{counter_association}'" unless has_many_association
30
+
31
+ if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
32
+ has_many_association = has_many_association.through_reflection
33
+ end
34
+
35
+ foreign_key = has_many_association.foreign_key.to_s
36
+ child_class = has_many_association.klass
37
+ reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
38
+ counter_name = reflection.counter_cache_column
39
+
40
+ unscoped.where(primary_key => object.id).update_all(
41
+ counter_name => object.send(counter_association).count(:all)
42
+ )
26
43
  end
44
+ return true
45
+ end
27
46
 
28
- if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
29
- has_many_association = has_many_association.through_reflection
47
+ # A generic "counter updater" implementation, intended primarily to be
48
+ # used by #increment_counter and #decrement_counter, but which may also
49
+ # be useful on its own. It simply does a direct SQL update for the record
50
+ # with the given ID, altering the given hash of counters by the amount
51
+ # given by the corresponding value:
52
+ #
53
+ # ==== Parameters
54
+ #
55
+ # * +id+ - The id of the object you wish to update a counter on or an array of ids.
56
+ # * +counters+ - A Hash containing the names of the fields
57
+ # to update as keys and the amount to update the field by as values.
58
+ #
59
+ # ==== Examples
60
+ #
61
+ # # For the Post with id of 5, decrement the comment_count by 1, and
62
+ # # increment the action_count by 1
63
+ # Post.update_counters 5, comment_count: -1, action_count: 1
64
+ # # Executes the following SQL:
65
+ # # UPDATE posts
66
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
67
+ # # action_count = COALESCE(action_count, 0) + 1
68
+ # # WHERE id = 5
69
+ #
70
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
71
+ # Post.update_counters [10, 15], comment_count: 1
72
+ # # Executes the following SQL:
73
+ # # UPDATE posts
74
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
75
+ # # WHERE id IN (10, 15)
76
+ def update_counters(id, counters)
77
+ updates = counters.map do |counter_name, value|
78
+ operator = value < 0 ? '-' : '+'
79
+ quoted_column = connection.quote_column_name(counter_name)
80
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
30
81
  end
31
82
 
32
- foreign_key = has_many_association.foreign_key.to_s
33
- child_class = has_many_association.klass
34
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
35
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
36
- counter_name = reflection.counter_cache_column
83
+ unscoped.where(primary_key => id).update_all updates.join(', ')
84
+ end
85
+
86
+ # Increment a numeric field by one, via a direct SQL update.
87
+ #
88
+ # This method is used primarily for maintaining counter_cache columns that are
89
+ # used to store aggregate values. For example, a +DiscussionBoard+ may cache
90
+ # posts_count and comments_count to avoid running an SQL query to calculate the
91
+ # number of posts and comments there are, each time it is displayed.
92
+ #
93
+ # ==== Parameters
94
+ #
95
+ # * +counter_name+ - The name of the field that should be incremented.
96
+ # * +id+ - The id of the object that should be incremented or an array of ids.
97
+ #
98
+ # ==== Examples
99
+ #
100
+ # # Increment the posts_count column for the record with an id of 5
101
+ # DiscussionBoard.increment_counter(:posts_count, 5)
102
+ def increment_counter(counter_name, id)
103
+ update_counters(id, counter_name => 1)
104
+ end
37
105
 
38
- stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
39
- arel_table[counter_name] => object.send(association).count
40
- })
41
- connection.update stmt
106
+ # Decrement a numeric field by one, via a direct SQL update.
107
+ #
108
+ # This works the same as #increment_counter but reduces the column value by
109
+ # 1 instead of increasing it.
110
+ #
111
+ # ==== Parameters
112
+ #
113
+ # * +counter_name+ - The name of the field that should be decremented.
114
+ # * +id+ - The id of the object that should be decremented or an array of ids.
115
+ #
116
+ # ==== Examples
117
+ #
118
+ # # Decrement the posts_count column for the record with an id of 5
119
+ # DiscussionBoard.decrement_counter(:posts_count, 5)
120
+ def decrement_counter(counter_name, id)
121
+ update_counters(id, counter_name => -1)
42
122
  end
43
- return true
44
123
  end
45
124
 
46
- # A generic "counter updater" implementation, intended primarily to be
47
- # used by increment_counter and decrement_counter, but which may also
48
- # be useful on its own. It simply does a direct SQL update for the record
49
- # with the given ID, altering the given hash of counters by the amount
50
- # given by the corresponding value:
51
- #
52
- # ==== Parameters
53
- #
54
- # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
55
- # * +counters+ - An Array of Hashes containing the names of the fields
56
- # to update as keys and the amount to update the field by as values.
57
- #
58
- # ==== Examples
59
- #
60
- # # For the Post with id of 5, decrement the comment_count by 1, and
61
- # # increment the action_count by 1
62
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
63
- # # Executes the following SQL:
64
- # # UPDATE posts
65
- # # SET comment_count = COALESCE(comment_count, 0) - 1,
66
- # # action_count = COALESCE(action_count, 0) + 1
67
- # # WHERE id = 5
68
- #
69
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
70
- # Post.update_counters [10, 15], :comment_count => 1
71
- # # Executes the following SQL:
72
- # # UPDATE posts
73
- # # SET comment_count = COALESCE(comment_count, 0) + 1
74
- # # WHERE id IN (10, 15)
75
- def update_counters(id, counters)
76
- updates = counters.map do |counter_name, value|
77
- operator = value < 0 ? '-' : '+'
78
- quoted_column = connection.quote_column_name(counter_name)
79
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
125
+ private
126
+
127
+ def _create_record(*)
128
+ id = super
129
+
130
+ each_counter_cached_associations do |association|
131
+ if send(association.reflection.name)
132
+ association.increment_counters
133
+ @_after_create_counter_called = true
134
+ end
135
+ end
136
+
137
+ id
80
138
  end
81
139
 
82
- IdentityMap.remove_by_id(symbolized_base_class, id) if IdentityMap.enabled?
140
+ def destroy_row
141
+ affected_rows = super
83
142
 
84
- update_all(updates.join(', '), primary_key => id )
85
- end
143
+ if affected_rows > 0
144
+ each_counter_cached_associations do |association|
145
+ foreign_key = association.reflection.foreign_key.to_sym
146
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
147
+ if send(association.reflection.name)
148
+ association.decrement_counters
149
+ end
150
+ end
151
+ end
152
+ end
86
153
 
87
- # Increment a number field by one, usually representing a count.
88
- #
89
- # This is used for caching aggregate values, so that they don't need to be computed every time.
90
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
91
- # shown it would have to run an SQL query to find how many posts and comments there are.
92
- #
93
- # ==== Parameters
94
- #
95
- # * +counter_name+ - The name of the field that should be incremented.
96
- # * +id+ - The id of the object that should be incremented.
97
- #
98
- # ==== Examples
99
- #
100
- # # Increment the post_count column for the record with an id of 5
101
- # DiscussionBoard.increment_counter(:post_count, 5)
102
- def increment_counter(counter_name, id)
103
- update_counters(id, counter_name => 1)
104
- end
154
+ affected_rows
155
+ end
156
+
157
+ def each_counter_cached_associations
158
+ _reflections.each do |name, reflection|
159
+ yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
160
+ end
161
+ end
105
162
 
106
- # Decrement a number field by one, usually representing a count.
107
- #
108
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
109
- #
110
- # ==== Parameters
111
- #
112
- # * +counter_name+ - The name of the field that should be decremented.
113
- # * +id+ - The id of the object that should be decremented.
114
- #
115
- # ==== Examples
116
- #
117
- # # Decrement the post_count column for the record with an id of 5
118
- # DiscussionBoard.decrement_counter(:post_count, 5)
119
- def decrement_counter(counter_name, id)
120
- update_counters(id, counter_name => -1)
121
- end
122
163
  end
123
164
  end
@@ -1,84 +1,121 @@
1
1
  module ActiveRecord
2
- module DynamicMatchers
3
- def respond_to?(method_id, include_private = false)
4
- if match = DynamicFinderMatch.match(method_id)
5
- return true if all_attributes_exists?(match.attribute_names)
6
- elsif match = DynamicScopeMatch.match(method_id)
7
- return true if all_attributes_exists?(match.attribute_names)
2
+ module DynamicMatchers #:nodoc:
3
+ def respond_to?(name, include_private = false)
4
+ if self == Base
5
+ super
6
+ else
7
+ match = Method.match(self, name)
8
+ match && match.valid? || super
8
9
  end
9
-
10
- super
11
10
  end
12
11
 
13
12
  private
14
13
 
15
- # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
16
- # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
17
- # section at the top of this file for more detailed information.
18
- #
19
- # It's even possible to use all the additional parameters to +find+. For example, the
20
- # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
21
- #
22
- # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
23
- # is first invoked, so that future attempts to use it do not run through method_missing.
24
- def method_missing(method_id, *arguments, &block)
25
- if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id))
26
- attribute_names = match.attribute_names
27
- super unless all_attributes_exists?(attribute_names)
28
- if !(match.is_a?(DynamicFinderMatch) && match.instantiator? && arguments.first.is_a?(Hash)) && arguments.size < attribute_names.size
29
- method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
30
- backtrace = [method_trace] + caller
31
- raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
32
- end
33
- if match.respond_to?(:scope?) && match.scope?
34
- self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
35
- def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
36
- attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
37
- #
38
- scoped(:conditions => attributes) # scoped(:conditions => attributes)
39
- end # end
40
- METHOD
41
- send(method_id, *arguments)
42
- elsif match.finder?
43
- options = if arguments.length > attribute_names.size
44
- arguments.extract_options!
45
- else
46
- {}
47
- end
48
-
49
- relation = options.any? ? scoped(options) : scoped
50
- relation.send :find_by_attributes, match, attribute_names, *arguments, &block
51
- elsif match.instantiator?
52
- scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
53
- end
14
+ def method_missing(name, *arguments, &block)
15
+ match = Method.match(self, name)
16
+
17
+ if match && match.valid?
18
+ match.define
19
+ send(name, *arguments, &block)
54
20
  else
55
21
  super
56
22
  end
57
23
  end
58
24
 
59
- # Similar in purpose to +expand_hash_conditions_for_aggregates+.
60
- def expand_attribute_names_for_aggregates(attribute_names)
61
- attribute_names.map { |attribute_name|
62
- unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
63
- aggregate_mapping(aggregation).map do |field_attr, _|
64
- field_attr.to_sym
65
- end
66
- else
67
- attribute_name.to_sym
25
+ class Method
26
+ @matchers = []
27
+
28
+ class << self
29
+ attr_reader :matchers
30
+
31
+ def match(model, name)
32
+ klass = matchers.find { |k| name =~ k.pattern }
33
+ klass.new(model, name) if klass
68
34
  end
69
- }.flatten
70
- end
71
35
 
72
- def all_attributes_exists?(attribute_names)
73
- (expand_attribute_names_for_aggregates(attribute_names) -
74
- column_methods_hash.keys).empty?
36
+ def pattern
37
+ @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
38
+ end
39
+
40
+ def prefix
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def suffix
45
+ ''
46
+ end
47
+ end
48
+
49
+ attr_reader :model, :name, :attribute_names
50
+
51
+ def initialize(model, name)
52
+ @model = model
53
+ @name = name.to_s
54
+ @attribute_names = @name.match(self.class.pattern)[1].split('_and_')
55
+ @attribute_names.map! { |n| @model.attribute_aliases[n] || n }
56
+ end
57
+
58
+ def valid?
59
+ attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
60
+ end
61
+
62
+ def define
63
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
64
+ def self.#{name}(#{signature})
65
+ #{body}
66
+ end
67
+ CODE
68
+ end
69
+
70
+ private
71
+
72
+ def body
73
+ "#{finder}(#{attributes_hash})"
74
+ end
75
+
76
+ # The parameters in the signature may have reserved Ruby words, in order
77
+ # to prevent errors, we start each param name with `_`.
78
+ def signature
79
+ attribute_names.map { |name| "_#{name}" }.join(', ')
80
+ end
81
+
82
+ # Given that the parameters starts with `_`, the finder needs to use the
83
+ # same parameter name.
84
+ def attributes_hash
85
+ "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}"
86
+ end
87
+
88
+ def finder
89
+ raise NotImplementedError
90
+ end
75
91
  end
76
92
 
77
- def aggregate_mapping(reflection)
78
- mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
79
- mapping.first.is_a?(Array) ? mapping : [mapping]
93
+ class FindBy < Method
94
+ Method.matchers << self
95
+
96
+ def self.prefix
97
+ "find_by"
98
+ end
99
+
100
+ def finder
101
+ "find_by"
102
+ end
80
103
  end
81
104
 
105
+ class FindByBang < Method
106
+ Method.matchers << self
107
+
108
+ def self.prefix
109
+ "find_by"
110
+ end
111
+
112
+ def self.suffix
113
+ "!"
114
+ end
82
115
 
116
+ def finder
117
+ "find_by!"
118
+ end
119
+ end
83
120
  end
84
121
  end