activerecord 3.2.22.5 → 4.0.0.beta1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  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 +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  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 +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  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/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,123 +1,121 @@
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 counter names 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)
31
24
 
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
25
+ if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
26
+ has_many_association = has_many_association.through_reflection
27
+ end
37
28
 
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
29
+ foreign_key = has_many_association.foreign_key.to_s
30
+ child_class = has_many_association.klass
31
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
32
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
33
+ counter_name = reflection.counter_cache_column
45
34
 
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}"
35
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
36
+ arel_table[counter_name] => object.send(association).count
37
+ })
38
+ connection.update stmt
39
+ end
40
+ return true
80
41
  end
81
42
 
82
- IdentityMap.remove_by_id(symbolized_base_class, id) if IdentityMap.enabled?
43
+ # A generic "counter updater" implementation, intended primarily to be
44
+ # used by increment_counter and decrement_counter, but which may also
45
+ # be useful on its own. It simply does a direct SQL update for the record
46
+ # with the given ID, altering the given hash of counters by the amount
47
+ # given by the corresponding value:
48
+ #
49
+ # ==== Parameters
50
+ #
51
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
52
+ # * +counters+ - An Array of Hashes containing the names of the fields
53
+ # to update as keys and the amount to update the field by as values.
54
+ #
55
+ # ==== Examples
56
+ #
57
+ # # For the Post with id of 5, decrement the comment_count by 1, and
58
+ # # increment the action_count by 1
59
+ # Post.update_counters 5, comment_count: -1, action_count: 1
60
+ # # Executes the following SQL:
61
+ # # UPDATE posts
62
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
63
+ # # action_count = COALESCE(action_count, 0) + 1
64
+ # # WHERE id = 5
65
+ #
66
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
67
+ # Post.update_counters [10, 15], comment_count: 1
68
+ # # Executes the following SQL:
69
+ # # UPDATE posts
70
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
71
+ # # WHERE id IN (10, 15)
72
+ def update_counters(id, counters)
73
+ updates = counters.map do |counter_name, value|
74
+ operator = value < 0 ? '-' : '+'
75
+ quoted_column = connection.quote_column_name(counter_name)
76
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
77
+ end
83
78
 
84
- update_all(updates.join(', '), primary_key => id )
85
- end
79
+ where(primary_key => id).update_all updates.join(', ')
80
+ end
86
81
 
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
82
+ # Increment a numeric field by one, via a direct SQL update.
83
+ #
84
+ # This method is used primarily for maintaining counter_cache columns used to
85
+ # store aggregate values. For example, a DiscussionBoard may cache posts_count
86
+ # and comments_count to avoid running an SQL query to calculate the number of
87
+ # posts and comments there are each time it is displayed.
88
+ #
89
+ # ==== Parameters
90
+ #
91
+ # * +counter_name+ - The name of the field that should be incremented.
92
+ # * +id+ - The id of the object that should be incremented or an Array of ids.
93
+ #
94
+ # ==== Examples
95
+ #
96
+ # # Increment the post_count column for the record with an id of 5
97
+ # DiscussionBoard.increment_counter(:post_count, 5)
98
+ def increment_counter(counter_name, id)
99
+ update_counters(id, counter_name => 1)
100
+ end
105
101
 
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)
102
+ # Decrement a numeric field by one, via a direct SQL update.
103
+ #
104
+ # This works the same as increment_counter but reduces the column value by
105
+ # 1 instead of increasing it.
106
+ #
107
+ # ==== Parameters
108
+ #
109
+ # * +counter_name+ - The name of the field that should be decremented.
110
+ # * +id+ - The id of the object that should be decremented or an Array of ids.
111
+ #
112
+ # ==== Examples
113
+ #
114
+ # # Decrement the post_count column for the record with an id of 5
115
+ # DiscussionBoard.decrement_counter(:post_count, 5)
116
+ def decrement_counter(counter_name, id)
117
+ update_counters(id, counter_name => -1)
118
+ end
121
119
  end
122
120
  end
123
121
  end
@@ -1,84 +1,131 @@
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
+ /^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/
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
+ # 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
75
99
  end
76
100
 
77
- def aggregate_mapping(reflection)
78
- mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
79
- mapping.first.is_a?(Array) ? mapping : [mapping]
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
80
112
  end
81
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
82
125
 
126
+ def finder
127
+ "find_by!"
128
+ end
129
+ end
83
130
  end
84
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,6 +53,10 @@ module ActiveRecord
53
53
  class RecordNotSaved < ActiveRecordError
54
54
  end
55
55
 
56
+ # Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
57
+ class RecordNotDestroyed < ActiveRecordError
58
+ end
59
+
56
60
  # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
57
61
  # MySQL when Ruby driver used is too old).
58
62
  class StatementInvalid < ActiveRecordError
@@ -102,13 +106,11 @@ module ActiveRecord
102
106
  attr_reader :record, :attempted_action
103
107
 
104
108
  def initialize(record, attempted_action)
109
+ super("Attempted to #{attempted_action} a stale object: #{record.class.name}")
105
110
  @record = record
106
111
  @attempted_action = attempted_action
107
112
  end
108
113
 
109
- def message
110
- "Attempted to #{attempted_action} a stale object: #{record.class.name}"
111
- end
112
114
  end
113
115
 
114
116
  # Raised when association is being configured improperly or
@@ -164,9 +166,9 @@ module ActiveRecord
164
166
  class AttributeAssignmentError < ActiveRecordError
165
167
  attr_reader :exception, :attribute
166
168
  def initialize(message, exception, attribute)
169
+ super(message)
167
170
  @exception = exception
168
171
  @attribute = attribute
169
- @message = message
170
172
  end
171
173
  end
172
174
 
@@ -185,11 +187,26 @@ module ActiveRecord
185
187
  attr_reader :model
186
188
 
187
189
  def initialize(model)
190
+ super("Unknown primary key for table #{model.table_name} in model #{model}.")
188
191
  @model = model
189
192
  end
190
193
 
191
- def message
192
- "Unknown primary key for table #{model.table_name} in model #{model}."
193
- end
194
+ end
195
+
196
+ # Raised when a relation cannot be mutated because it's already loaded.
197
+ #
198
+ # class Task < ActiveRecord::Base
199
+ # end
200
+ #
201
+ # relation = Task.all
202
+ # relation.loaded? # => true
203
+ #
204
+ # # Methods which try to mutate a loaded relation fail.
205
+ # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
206
+ # relation.limit!(5) # => ActiveRecord::ImmutableRelation
207
+ class ImmutableRelation < ActiveRecordError
208
+ end
209
+
210
+ class TransactionIsolationError < ActiveRecordError
194
211
  end
195
212
  end