activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,123 +1,122 @@
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)
4
+ extend ActiveSupport::Concern
21
5
 
22
- if has_many_association.options[:as]
23
- has_many_association.options[:as].to_s.classify
24
- else
25
- self.name
26
- end
27
-
28
- if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
29
- has_many_association = has_many_association.through_reflection
30
- end
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
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 |association|
23
+ has_many_association = reflect_on_association(association.to_sym)
24
+ raise ArgumentError, "'#{self.name}' has no association called '#{association}'" unless has_many_association
31
25
 
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
26
+ if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
27
+ has_many_association = has_many_association.through_reflection
28
+ end
37
29
 
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
42
- end
43
- return true
44
- end
30
+ foreign_key = has_many_association.foreign_key.to_s
31
+ child_class = has_many_association.klass
32
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
33
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
34
+ counter_name = reflection.counter_cache_column
45
35
 
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}"
36
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
37
+ arel_table[counter_name] => object.send(association).count(:all)
38
+ })
39
+ connection.update stmt
40
+ end
41
+ return true
80
42
  end
81
43
 
82
- IdentityMap.remove_by_id(symbolized_base_class, id) if IdentityMap.enabled?
44
+ # A generic "counter updater" implementation, intended primarily to be
45
+ # used by increment_counter and decrement_counter, but which may also
46
+ # be useful on its own. It simply does a direct SQL update for the record
47
+ # with the given ID, altering the given hash of counters by the amount
48
+ # given by the corresponding value:
49
+ #
50
+ # ==== Parameters
51
+ #
52
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
53
+ # * +counters+ - An Array of Hashes containing the names of the fields
54
+ # to update as keys and the amount to update the field by as values.
55
+ #
56
+ # ==== Examples
57
+ #
58
+ # # For the Post with id of 5, decrement the comment_count by 1, and
59
+ # # increment the action_count by 1
60
+ # Post.update_counters 5, comment_count: -1, action_count: 1
61
+ # # Executes the following SQL:
62
+ # # UPDATE posts
63
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
64
+ # # action_count = COALESCE(action_count, 0) + 1
65
+ # # WHERE id = 5
66
+ #
67
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
68
+ # Post.update_counters [10, 15], comment_count: 1
69
+ # # Executes the following SQL:
70
+ # # UPDATE posts
71
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
72
+ # # WHERE id IN (10, 15)
73
+ def update_counters(id, counters)
74
+ updates = counters.map do |counter_name, value|
75
+ operator = value < 0 ? '-' : '+'
76
+ quoted_column = connection.quote_column_name(counter_name)
77
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
78
+ end
83
79
 
84
- update_all(updates.join(', '), primary_key => id )
85
- end
80
+ unscoped.where(primary_key => id).update_all updates.join(', ')
81
+ end
86
82
 
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
83
+ # Increment a numeric field by one, via a direct SQL update.
84
+ #
85
+ # This method is used primarily for maintaining counter_cache columns used to
86
+ # store aggregate values. For example, a DiscussionBoard may cache posts_count
87
+ # and comments_count to avoid running an SQL query to calculate the number of
88
+ # posts and comments there are each time it is displayed.
89
+ #
90
+ # ==== Parameters
91
+ #
92
+ # * +counter_name+ - The name of the field that should be incremented.
93
+ # * +id+ - The id of the object that should be incremented or an Array of ids.
94
+ #
95
+ # ==== Examples
96
+ #
97
+ # # Increment the post_count column for the record with an id of 5
98
+ # DiscussionBoard.increment_counter(:post_count, 5)
99
+ def increment_counter(counter_name, id)
100
+ update_counters(id, counter_name => 1)
101
+ end
105
102
 
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)
103
+ # Decrement a numeric field by one, via a direct SQL update.
104
+ #
105
+ # This works the same as increment_counter but reduces the column value by
106
+ # 1 instead of increasing it.
107
+ #
108
+ # ==== Parameters
109
+ #
110
+ # * +counter_name+ - The name of the field that should be decremented.
111
+ # * +id+ - The id of the object that should be decremented or an Array of ids.
112
+ #
113
+ # ==== Examples
114
+ #
115
+ # # Decrement the post_count column for the record with an id of 5
116
+ # DiscussionBoard.decrement_counter(:post_count, 5)
117
+ def decrement_counter(counter_name, id)
118
+ update_counters(id, counter_name => -1)
119
+ end
121
120
  end
122
121
  end
123
122
  end
@@ -1,84 +1,136 @@
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)
8
- end
2
+ module DynamicMatchers #:nodoc:
3
+ # This code in this file seems to have a lot of indirection, but the indirection
4
+ # is there to provide extension points for the activerecord-deprecated_finders
5
+ # gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
6
+ # then we can remove the indirection.
9
7
 
10
- super
8
+ def respond_to?(name, include_private = false)
9
+ match = Method.match(self, name)
10
+ match && match.valid? || super
11
11
  end
12
12
 
13
13
  private
14
14
 
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
15
+ def method_missing(name, *arguments, &block)
16
+ match = Method.match(self, name)
17
+
18
+ if match && match.valid?
19
+ match.define
20
+ send(name, *arguments, &block)
54
21
  else
55
22
  super
56
23
  end
57
24
  end
58
25
 
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
26
+ class Method
27
+ @matchers = []
28
+
29
+ class << self
30
+ attr_reader :matchers
31
+
32
+ def match(model, name)
33
+ klass = matchers.find { |k| name =~ k.pattern }
34
+ klass.new(model, name) if klass
35
+ end
36
+
37
+ def pattern
38
+ @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
39
+ end
40
+
41
+ def prefix
42
+ raise NotImplementedError
68
43
  end
69
- }.flatten
44
+
45
+ def suffix
46
+ ''
47
+ end
48
+ end
49
+
50
+ attr_reader :model, :name, :attribute_names
51
+
52
+ def initialize(model, name)
53
+ @model = model
54
+ @name = name.to_s
55
+ @attribute_names = @name.match(self.class.pattern)[1].split('_and_')
56
+ @attribute_names.map! { |n| @model.attribute_aliases[n] || n }
57
+ end
58
+
59
+ def valid?
60
+ attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
61
+ end
62
+
63
+ def define
64
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
65
+ def self.#{name}(#{signature})
66
+ #{body}
67
+ end
68
+ CODE
69
+ end
70
+
71
+ def body
72
+ raise NotImplementedError
73
+ end
70
74
  end
71
75
 
72
- def all_attributes_exists?(attribute_names)
73
- (expand_attribute_names_for_aggregates(attribute_names) -
74
- column_methods_hash.keys).empty?
76
+ module Finder
77
+ # Extended in activerecord-deprecated_finders
78
+ def body
79
+ result
80
+ end
81
+
82
+ # Extended in activerecord-deprecated_finders
83
+ def result
84
+ "#{finder}(#{attributes_hash})"
85
+ end
86
+
87
+ # The parameters in the signature may have reserved Ruby words, in order
88
+ # to prevent errors, we start each param name with `_`.
89
+ #
90
+ # Extended in activerecord-deprecated_finders
91
+ def signature
92
+ attribute_names.map { |name| "_#{name}" }.join(', ')
93
+ end
94
+
95
+ # Given that the parameters starts with `_`, the finder needs to use the
96
+ # same parameter name.
97
+ def attributes_hash
98
+ "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}"
99
+ end
100
+
101
+ def finder
102
+ raise NotImplementedError
103
+ end
75
104
  end
76
105
 
77
- def aggregate_mapping(reflection)
78
- mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
79
- mapping.first.is_a?(Array) ? mapping : [mapping]
106
+ class FindBy < Method
107
+ Method.matchers << self
108
+ include Finder
109
+
110
+ def self.prefix
111
+ "find_by"
112
+ end
113
+
114
+ def finder
115
+ "find_by"
116
+ end
80
117
  end
81
118
 
119
+ class FindByBang < Method
120
+ Method.matchers << self
121
+ include Finder
122
+
123
+ def self.prefix
124
+ "find_by"
125
+ end
126
+
127
+ def self.suffix
128
+ "!"
129
+ end
82
130
 
131
+ def finder
132
+ "find_by!"
133
+ end
134
+ end
83
135
  end
84
136
  end
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  # end
23
23
  #
24
24
  # # Comments are not patches, this assignment raises AssociationTypeMismatch.
25
- # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
25
+ # @ticket.patches << Comment.new(content: "Please attach tests to your patch.")
26
26
  class AssociationTypeMismatch < ActiveRecordError
27
27
  end
28
28
 
@@ -53,24 +53,29 @@ module ActiveRecord
53
53
  class RecordNotSaved < ActiveRecordError
54
54
  end
55
55
 
56
- # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
57
- # MySQL when Ruby driver used is too old).
56
+ # Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
57
+ class RecordNotDestroyed < ActiveRecordError
58
+ end
59
+
60
+ # Superclass for all database execution errors.
61
+ #
62
+ # Wraps the underlying database error as +original_exception+.
58
63
  class StatementInvalid < ActiveRecordError
64
+ attr_reader :original_exception
65
+
66
+ def initialize(message, original_exception = nil)
67
+ super(message)
68
+ @original_exception = original_exception
69
+ end
59
70
  end
60
71
 
61
72
  # Raised when SQL statement is invalid and the application gets a blank result.
62
73
  class ThrowResult < ActiveRecordError
63
74
  end
64
75
 
65
- # Parent class for all specific exceptions which wrap database driver exceptions
66
- # provides access to the original exception also.
76
+ # Defunct wrapper class kept for compatibility.
77
+ # +StatementInvalid+ wraps the original exception now.
67
78
  class WrappedDatabaseException < StatementInvalid
68
- attr_reader :original_exception
69
-
70
- def initialize(message, original_exception)
71
- super(message)
72
- @original_exception = original_exception
73
- end
74
79
  end
75
80
 
76
81
  # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
@@ -102,13 +107,11 @@ module ActiveRecord
102
107
  attr_reader :record, :attempted_action
103
108
 
104
109
  def initialize(record, attempted_action)
110
+ super("Attempted to #{attempted_action} a stale object: #{record.class.name}")
105
111
  @record = record
106
112
  @attempted_action = attempted_action
107
113
  end
108
114
 
109
- def message
110
- "Attempted to #{attempted_action} a stale object: #{record.class.name}"
111
- end
112
115
  end
113
116
 
114
117
  # Raised when association is being configured improperly or
@@ -164,9 +167,9 @@ module ActiveRecord
164
167
  class AttributeAssignmentError < ActiveRecordError
165
168
  attr_reader :exception, :attribute
166
169
  def initialize(message, exception, attribute)
170
+ super(message)
167
171
  @exception = exception
168
172
  @attribute = attribute
169
- @message = message
170
173
  end
171
174
  end
172
175
 
@@ -185,11 +188,26 @@ module ActiveRecord
185
188
  attr_reader :model
186
189
 
187
190
  def initialize(model)
191
+ super("Unknown primary key for table #{model.table_name} in model #{model}.")
188
192
  @model = model
189
193
  end
190
194
 
191
- def message
192
- "Unknown primary key for table #{model.table_name} in model #{model}."
193
- end
195
+ end
196
+
197
+ # Raised when a relation cannot be mutated because it's already loaded.
198
+ #
199
+ # class Task < ActiveRecord::Base
200
+ # end
201
+ #
202
+ # relation = Task.all
203
+ # relation.loaded? # => true
204
+ #
205
+ # # Methods which try to mutate a loaded relation fail.
206
+ # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
207
+ # relation.limit!(5) # => ActiveRecord::ImmutableRelation
208
+ class ImmutableRelation < ActiveRecordError
209
+ end
210
+
211
+ class TransactionIsolationError < ActiveRecordError
194
212
  end
195
213
  end
@@ -1,62 +1,22 @@
1
- require 'active_support/core_ext/class/attribute'
1
+ require 'active_support/lazy_load_hooks'
2
+ require 'active_record/explain_registry'
2
3
 
3
4
  module ActiveRecord
4
5
  module Explain
5
- def self.extended(base)
6
- base.class_eval do
7
- # If a query takes longer than these many seconds we log its query plan
8
- # automatically. nil disables this feature.
9
- class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false
10
- self.auto_explain_threshold_in_seconds = nil
11
- end
12
- end
13
-
14
- # If the database adapter supports explain and auto explain is enabled,
15
- # this method triggers EXPLAIN logging for the queries triggered by the
16
- # block if it takes more than the threshold as a whole. That is, the
17
- # threshold is not checked against each individual query, but against the
18
- # duration of the entire block. This approach is convenient for relations.
19
-
20
- #
21
- # The available_queries_for_explain thread variable collects the queries
22
- # to be explained. If the value is nil, it means queries are not being
23
- # currently collected. A false value indicates collecting is turned
24
- # off. Otherwise it is an array of queries.
25
- def logging_query_plan # :nodoc:
26
- return yield unless logger
27
-
28
- threshold = auto_explain_threshold_in_seconds
29
- current = Thread.current
30
- if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil?
31
- begin
32
- queries = current[:available_queries_for_explain] = []
33
- start = Time.now
34
- result = yield
35
- logger.warn(exec_explain(queries)) if Time.now - start > threshold
36
- result
37
- ensure
38
- current[:available_queries_for_explain] = nil
39
- end
40
- else
41
- yield
42
- end
43
- end
44
-
45
- # Relation#explain needs to be able to collect the queries regardless of
46
- # whether auto explain is enabled. This method serves that purpose.
6
+ # Executes the block with the collect flag enabled. Queries are collected
7
+ # asynchronously by the subscriber and returned.
47
8
  def collecting_queries_for_explain # :nodoc:
48
- current = Thread.current
49
- original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
50
- return yield, current[:available_queries_for_explain]
9
+ ExplainRegistry.collect = true
10
+ yield
11
+ ExplainRegistry.queries
51
12
  ensure
52
- # Note that the return value above does not depend on this assigment.
53
- current[:available_queries_for_explain] = original
13
+ ExplainRegistry.reset
54
14
  end
55
15
 
56
16
  # Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
57
17
  # Returns a formatted string ready to be logged.
58
18
  def exec_explain(queries) # :nodoc:
59
- queries && queries.map do |sql, bind|
19
+ str = queries.map do |sql, bind|
60
20
  [].tap do |msg|
61
21
  msg << "EXPLAIN for: #{sql}"
62
22
  unless bind.empty?
@@ -66,21 +26,13 @@ module ActiveRecord
66
26
  msg << connection.explain(sql, bind)
67
27
  end.join("\n")
68
28
  end.join("\n")
69
- end
70
29
 
71
- # Silences automatic EXPLAIN logging for the duration of the block.
72
- #
73
- # This has high priority, no EXPLAINs will be run even if downwards
74
- # the threshold is set to 0.
75
- #
76
- # As the name of the method suggests this only applies to automatic
77
- # EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run.
78
- def silence_auto_explain
79
- current = Thread.current
80
- original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
81
- yield
82
- ensure
83
- current[:available_queries_for_explain] = original
30
+ # Overriding inspect to be more human readable, specially in the console.
31
+ def str.inspect
32
+ self
33
+ end
34
+
35
+ str
84
36
  end
85
37
  end
86
38
  end
@@ -0,0 +1,30 @@
1
+ require 'active_support/per_thread_registry'
2
+
3
+ module ActiveRecord
4
+ # This is a thread locals registry for EXPLAIN. For example
5
+ #
6
+ # ActiveRecord::ExplainRegistry.queries
7
+ #
8
+ # returns the collected queries local to the current thread.
9
+ #
10
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
11
+ # for further details.
12
+ class ExplainRegistry # :nodoc:
13
+ extend ActiveSupport::PerThreadRegistry
14
+
15
+ attr_accessor :queries, :collect
16
+
17
+ def initialize
18
+ reset
19
+ end
20
+
21
+ def collect?
22
+ @collect
23
+ end
24
+
25
+ def reset
26
+ @collect = false
27
+ @queries = []
28
+ end
29
+ end
30
+ end