activerecord 3.0.0 → 4.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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,115 +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
- expected_name = if has_many_association.options[:as]
23
- has_many_association.options[:as].to_s.classify
24
- else
25
- self.name
26
- 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
27
25
 
28
- child_class = has_many_association.klass
29
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
30
- reflection = belongs_to.find { |e| e.class_name == expected_name }
31
- 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
32
29
 
33
- self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({
34
- arel_table[counter_name] => object.send(association).count
35
- })
36
- end
37
- return true
38
- 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
39
35
 
40
- # A generic "counter updater" implementation, intended primarily to be
41
- # used by increment_counter and decrement_counter, but which may also
42
- # be useful on its own. It simply does a direct SQL update for the record
43
- # with the given ID, altering the given hash of counters by the amount
44
- # given by the corresponding value:
45
- #
46
- # ==== Parameters
47
- #
48
- # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
49
- # * +counters+ - An Array of Hashes containing the names of the fields
50
- # to update as keys and the amount to update the field by as values.
51
- #
52
- # ==== Examples
53
- #
54
- # # For the Post with id of 5, decrement the comment_count by 1, and
55
- # # increment the action_count by 1
56
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
57
- # # Executes the following SQL:
58
- # # UPDATE posts
59
- # # SET comment_count = comment_count - 1,
60
- # # action_count = action_count + 1
61
- # # WHERE id = 5
62
- #
63
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
64
- # Post.update_counters [10, 15], :comment_count => 1
65
- # # Executes the following SQL:
66
- # # UPDATE posts
67
- # # SET comment_count = comment_count + 1,
68
- # # WHERE id IN (10, 15)
69
- def update_counters(id, counters)
70
- updates = counters.map do |counter_name, value|
71
- operator = value < 0 ? '-' : '+'
72
- quoted_column = connection.quote_column_name(counter_name)
73
- "#{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
38
+ })
39
+ connection.update stmt
40
+ end
41
+ return true
74
42
  end
75
43
 
76
- update_all(updates.join(', '), primary_key => id )
77
- end
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
78
79
 
79
- # Increment a number field by one, usually representing a count.
80
- #
81
- # This is used for caching aggregate values, so that they don't need to be computed every time.
82
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
83
- # shown it would have to run an SQL query to find how many posts and comments there are.
84
- #
85
- # ==== Parameters
86
- #
87
- # * +counter_name+ - The name of the field that should be incremented.
88
- # * +id+ - The id of the object that should be incremented.
89
- #
90
- # ==== Examples
91
- #
92
- # # Increment the post_count column for the record with an id of 5
93
- # DiscussionBoard.increment_counter(:post_count, 5)
94
- def increment_counter(counter_name, id)
95
- update_counters(id, counter_name => 1)
96
- end
80
+ where(primary_key => id).update_all updates.join(', ')
81
+ end
97
82
 
98
- # Decrement a number field by one, usually representing a count.
99
- #
100
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
101
- #
102
- # ==== Parameters
103
- #
104
- # * +counter_name+ - The name of the field that should be decremented.
105
- # * +id+ - The id of the object that should be decremented.
106
- #
107
- # ==== Examples
108
- #
109
- # # Decrement the post_count column for the record with an id of 5
110
- # DiscussionBoard.decrement_counter(:post_count, 5)
111
- def decrement_counter(counter_name, id)
112
- update_counters(id, counter_name => -1)
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
102
+
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
113
120
  end
114
121
  end
115
122
  end
@@ -0,0 +1,131 @@
1
+ module ActiveRecord
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.
7
+
8
+ def respond_to?(name, include_private = false)
9
+ match = Method.match(self, name)
10
+ match && match.valid? || super
11
+ end
12
+
13
+ private
14
+
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)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
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
+ /^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/
39
+ end
40
+
41
+ def prefix
42
+ raise NotImplementedError
43
+ end
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
74
+ end
75
+
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
+ # Extended in activerecord-deprecated_finders
88
+ def signature
89
+ attribute_names.join(', ')
90
+ end
91
+
92
+ def attributes_hash
93
+ "{" + attribute_names.map { |name| ":#{name} => #{name}" }.join(',') + "}"
94
+ end
95
+
96
+ def finder
97
+ raise NotImplementedError
98
+ end
99
+ end
100
+
101
+ class FindBy < Method
102
+ Method.matchers << self
103
+ include Finder
104
+
105
+ def self.prefix
106
+ "find_by"
107
+ end
108
+
109
+ def finder
110
+ "find_by"
111
+ end
112
+ end
113
+
114
+ class FindByBang < Method
115
+ Method.matchers << self
116
+ include Finder
117
+
118
+ def self.prefix
119
+ "find_by"
120
+ end
121
+
122
+ def self.suffix
123
+ "!"
124
+ end
125
+
126
+ def finder
127
+ "find_by!"
128
+ end
129
+ end
130
+ end
131
+ 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.
@@ -87,7 +92,7 @@ module ActiveRecord
87
92
  #
88
93
  # For example, in
89
94
  #
90
- # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
95
+ # Location.where("lat = ? AND lng = ?", 53.7362)
91
96
  #
92
97
  # two placeholders are given but only one variable to fill them.
93
98
  class PreparedStatementInvalid < ActiveRecordError
@@ -99,6 +104,14 @@ module ActiveRecord
99
104
  #
100
105
  # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
101
106
  class StaleObjectError < ActiveRecordError
107
+ attr_reader :record, :attempted_action
108
+
109
+ def initialize(record, attempted_action)
110
+ super("Attempted to #{attempted_action} a stale object: #{record.class.name}")
111
+ @record = record
112
+ @attempted_action = attempted_action
113
+ end
114
+
102
115
  end
103
116
 
104
117
  # Raised when association is being configured improperly or
@@ -154,9 +167,9 @@ module ActiveRecord
154
167
  class AttributeAssignmentError < ActiveRecordError
155
168
  attr_reader :exception, :attribute
156
169
  def initialize(message, exception, attribute)
170
+ super(message)
157
171
  @exception = exception
158
172
  @attribute = attribute
159
- @message = message
160
173
  end
161
174
  end
162
175
 
@@ -169,4 +182,32 @@ module ActiveRecord
169
182
  @errors = errors
170
183
  end
171
184
  end
185
+
186
+ # Raised when a primary key is needed, but there is not one specified in the schema or model.
187
+ class UnknownPrimaryKey < ActiveRecordError
188
+ attr_reader :model
189
+
190
+ def initialize(model)
191
+ super("Unknown primary key for table #{model.table_name} in model #{model}.")
192
+ @model = model
193
+ end
194
+
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
212
+ end
172
213
  end
@@ -0,0 +1,38 @@
1
+ require 'active_support/lazy_load_hooks'
2
+ require 'active_record/explain_registry'
3
+
4
+ module ActiveRecord
5
+ module Explain
6
+ # Executes the block with the collect flag enabled. Queries are collected
7
+ # asynchronously by the subscriber and returned.
8
+ def collecting_queries_for_explain # :nodoc:
9
+ ExplainRegistry.collect = true
10
+ yield
11
+ ExplainRegistry.queries
12
+ ensure
13
+ ExplainRegistry.reset
14
+ end
15
+
16
+ # Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
17
+ # Returns a formatted string ready to be logged.
18
+ def exec_explain(queries) # :nodoc:
19
+ str = queries.map do |sql, bind|
20
+ [].tap do |msg|
21
+ msg << "EXPLAIN for: #{sql}"
22
+ unless bind.empty?
23
+ bind_msg = bind.map {|col, val| [col.name, val]}.inspect
24
+ msg.last << " #{bind_msg}"
25
+ end
26
+ msg << connection.explain(sql, bind)
27
+ end.join("\n")
28
+ end.join("\n")
29
+
30
+ # Overriding inspect to be more human readable, specially in the console.
31
+ def str.inspect
32
+ self
33
+ end
34
+
35
+ str
36
+ end
37
+ end
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
@@ -0,0 +1,29 @@
1
+ require 'active_support/notifications'
2
+ require 'active_record/explain_registry'
3
+
4
+ module ActiveRecord
5
+ class ExplainSubscriber # :nodoc:
6
+ def start(name, id, payload)
7
+ # unused
8
+ end
9
+
10
+ def finish(name, id, payload)
11
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
12
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
13
+ end
14
+ end
15
+
16
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
17
+ # our own EXPLAINs now matter how loopingly beautiful that would be.
18
+ #
19
+ # On the other hand, we want to monitor the performance of our real database
20
+ # queries, not the performance of the access to the query cache.
21
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
22
+ EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i
23
+ def ignore_payload?(payload)
24
+ payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
25
+ end
26
+
27
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module ActiveRecord
5
+ class FixtureSet
6
+ class File # :nodoc:
7
+ include Enumerable
8
+
9
+ ##
10
+ # Open a fixture file named +file+. When called with a block, the block
11
+ # is called with the filehandle and the filehandle is automatically closed
12
+ # when the block finishes.
13
+ def self.open(file)
14
+ x = new file
15
+ block_given? ? yield(x) : x
16
+ end
17
+
18
+ def initialize(file)
19
+ @file = file
20
+ @rows = nil
21
+ end
22
+
23
+ def each(&block)
24
+ rows.each(&block)
25
+ end
26
+
27
+
28
+ private
29
+ def rows
30
+ return @rows if @rows
31
+
32
+ begin
33
+ data = YAML.load(render(IO.read(@file)))
34
+ rescue ArgumentError, Psych::SyntaxError => error
35
+ raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
36
+ end
37
+ @rows = data ? validate(data).to_a : []
38
+ end
39
+
40
+ def render(content)
41
+ ERB.new(content).result
42
+ end
43
+
44
+ # Validate our unmarshalled data.
45
+ def validate(data)
46
+ unless Hash === data || YAML::Omap === data
47
+ raise Fixture::FormatError, 'fixture is not a hash'
48
+ end
49
+
50
+ raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
51
+ data
52
+ end
53
+ end
54
+ end
55
+ end