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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2799 -617
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -32
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations/alias_tracker.rb +4 -2
- data/lib/active_record/associations/association.rb +60 -46
- data/lib/active_record/associations/association_scope.rb +46 -40
- data/lib/active_record/associations/belongs_to_association.rb +17 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +73 -56
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +130 -96
- data/lib/active_record/associations/collection_proxy.rb +916 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
- data/lib/active_record/associations/has_many_association.rb +35 -8
- data/lib/active_record/associations/has_many_through_association.rb +37 -17
- data/lib/active_record/associations/has_one_association.rb +42 -19
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
- data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
- data/lib/active_record/associations/join_dependency.rb +30 -9
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/preloader.rb +20 -43
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +223 -282
- data/lib/active_record/attribute_assignment.rb +134 -154
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +36 -29
- data/lib/active_record/attribute_methods/primary_key.rb +45 -31
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +67 -90
- data/lib/active_record/attribute_methods/serialization.rb +133 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
- data/lib/active_record/attribute_methods/write.rb +34 -39
- data/lib/active_record/attribute_methods.rb +268 -108
- data/lib/active_record/autosave_association.rb +80 -73
- data/lib/active_record/base.rb +54 -451
- data/lib/active_record/callbacks.rb +60 -22
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
- data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
- data/lib/active_record/connection_adapters/column.rb +67 -36
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
- data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
- data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +472 -0
- data/lib/active_record/counter_cache.rb +107 -108
- data/lib/active_record/dynamic_matchers.rb +115 -63
- data/lib/active_record/errors.rb +36 -18
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +8 -4
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +159 -155
- data/lib/active_record/inheritance.rb +93 -59
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +39 -43
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration/command_recorder.rb +102 -33
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +411 -173
- data/lib/active_record/model_schema.rb +81 -94
- data/lib/active_record/nested_attributes.rb +173 -131
- data/lib/active_record/null_relation.rb +67 -0
- data/lib/active_record/persistence.rb +254 -106
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +113 -38
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +115 -368
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +110 -61
- data/lib/active_record/relation/batches.rb +29 -29
- data/lib/active_record/relation/calculations.rb +155 -125
- data/lib/active_record/relation/delegation.rb +94 -18
- data/lib/active_record/relation/finder_methods.rb +151 -203
- data/lib/active_record/relation/merger.rb +188 -0
- data/lib/active_record/relation/predicate_builder.rb +85 -42
- data/lib/active_record/relation/query_methods.rb +793 -146
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/relation.rb +293 -173
- data/lib/active_record/result.rb +48 -7
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +41 -54
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +41 -41
- data/lib/active_record/schema_migration.rb +46 -0
- data/lib/active_record/scoping/default.rb +56 -52
- data/lib/active_record/scoping/named.rb +78 -103
- data/lib/active_record/scoping.rb +54 -124
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +131 -15
- data/lib/active_record/tasks/database_tasks.rb +204 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +67 -38
- data/lib/active_record/timestamp.rb +16 -11
- data/lib/active_record/transactions.rb +73 -51
- data/lib/active_record/validations/associated.rb +19 -13
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +110 -57
- data/lib/active_record/validations.rb +18 -17
- data/lib/active_record/version.rb +7 -6
- data/lib/active_record.rb +63 -45
- data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -5
- metadata +43 -29
- data/examples/associations.png +0 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- 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
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
80
|
+
unscoped.where(primary_key => id).update_all updates.join(', ')
|
81
|
+
end
|
86
82
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
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
|
data/lib/active_record/errors.rb
CHANGED
@@ -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(:
|
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
|
57
|
-
|
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
|
-
#
|
66
|
-
#
|
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
|
-
|
192
|
-
|
193
|
-
|
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/
|
1
|
+
require 'active_support/lazy_load_hooks'
|
2
|
+
require 'active_record/explain_registry'
|
2
3
|
|
3
4
|
module ActiveRecord
|
4
5
|
module Explain
|
5
|
-
|
6
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
9
|
+
ExplainRegistry.collect = true
|
10
|
+
yield
|
11
|
+
ExplainRegistry.queries
|
51
12
|
ensure
|
52
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|