activerecord 3.1.11 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. data/CHANGELOG.md +6294 -97
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record/aggregations.rb +2 -2
  5. data/lib/active_record/associations/association.rb +2 -42
  6. data/lib/active_record/associations/association_scope.rb +3 -30
  7. data/lib/active_record/associations/builder/association.rb +6 -4
  8. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  10. data/lib/active_record/associations/builder/has_many.rb +4 -4
  11. data/lib/active_record/associations/builder/has_one.rb +5 -6
  12. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  13. data/lib/active_record/associations/collection_association.rb +55 -28
  14. data/lib/active_record/associations/collection_proxy.rb +1 -35
  15. data/lib/active_record/associations/has_many_association.rb +5 -1
  16. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  17. data/lib/active_record/associations/join_dependency.rb +1 -1
  18. data/lib/active_record/associations/preloader/association.rb +3 -1
  19. data/lib/active_record/associations.rb +82 -69
  20. data/lib/active_record/attribute_assignment.rb +221 -0
  21. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  23. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  24. data/lib/active_record/attribute_methods/read.rb +72 -83
  25. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  27. data/lib/active_record/attribute_methods/write.rb +27 -5
  28. data/lib/active_record/attribute_methods.rb +209 -30
  29. data/lib/active_record/autosave_association.rb +23 -8
  30. data/lib/active_record/base.rb +217 -1709
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  33. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  34. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/quoting.rb +9 -12
  36. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  37. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +43 -22
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  40. data/lib/active_record/connection_adapters/column.rb +2 -2
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +4 -3
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures/file.rb +65 -0
  53. data/lib/active_record/fixtures.rb +31 -76
  54. data/lib/active_record/identity_map.rb +4 -11
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +30 -25
  58. data/lib/active_record/locking/pessimistic.rb +23 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration/command_recorder.rb +8 -8
  61. data/lib/active_record/migration.rb +47 -30
  62. data/lib/active_record/model_schema.rb +366 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -9
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +134 -77
  69. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  70. data/lib/active_record/readonly_attributes.rb +26 -0
  71. data/lib/active_record/reflection.rb +7 -15
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +6 -5
  76. data/lib/active_record/relation/predicate_builder.rb +12 -19
  77. data/lib/active_record/relation/query_methods.rb +76 -10
  78. data/lib/active_record/relation/spawn_methods.rb +11 -2
  79. data/lib/active_record/relation.rb +77 -34
  80. data/lib/active_record/result.rb +1 -1
  81. data/lib/active_record/sanitization.rb +194 -0
  82. data/lib/active_record/schema_dumper.rb +5 -2
  83. data/lib/active_record/scoping/default.rb +142 -0
  84. data/lib/active_record/scoping/named.rb +202 -0
  85. data/lib/active_record/scoping.rb +152 -0
  86. data/lib/active_record/serialization.rb +1 -43
  87. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  88. data/lib/active_record/session_store.rb +15 -15
  89. data/lib/active_record/store.rb +50 -0
  90. data/lib/active_record/test_case.rb +11 -7
  91. data/lib/active_record/timestamp.rb +16 -3
  92. data/lib/active_record/transactions.rb +5 -5
  93. data/lib/active_record/translation.rb +22 -0
  94. data/lib/active_record/validations/associated.rb +5 -4
  95. data/lib/active_record/validations/uniqueness.rb +4 -4
  96. data/lib/active_record/validations.rb +1 -1
  97. data/lib/active_record/version.rb +2 -2
  98. data/lib/active_record.rb +28 -2
  99. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  100. data/lib/rails/generators/active_record/migration/templates/migration.rb +9 -3
  101. data/lib/rails/generators/active_record/model/model_generator.rb +5 -1
  102. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  103. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  104. metadata +50 -40
  105. checksums.yaml +0 -7
  106. data/lib/active_record/named_scope.rb +0 -200
@@ -83,7 +83,7 @@ module ActiveRecord
83
83
  #
84
84
  # Example for find with a lock: Imagine two concurrent transactions:
85
85
  # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
86
- # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
86
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
87
87
  # transaction has to wait until the first is finished; we get the
88
88
  # expected <tt>person.visits == 4</tt>.
89
89
  #
@@ -134,7 +134,7 @@ module ActiveRecord
134
134
  def last(*args)
135
135
  if args.any?
136
136
  if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
137
- if order_values.empty? && reorder_value.nil?
137
+ if order_values.empty?
138
138
  order("#{primary_key} DESC").limit(*args).reverse
139
139
  else
140
140
  to_a.last(*args)
@@ -191,7 +191,7 @@ module ActiveRecord
191
191
 
192
192
  join_dependency = construct_join_dependency_for_association_find
193
193
  relation = construct_relation_for_association_find(join_dependency)
194
- relation = relation.except(:select).select("1").limit(1)
194
+ relation = relation.except(:select, :order).select("1").limit(1)
195
195
 
196
196
  case id
197
197
  when Array, Hash
@@ -200,7 +200,7 @@ module ActiveRecord
200
200
  relation = relation.where(table[primary_key].eq(id)) if id
201
201
  end
202
202
 
203
- connection.select_value(relation) ? true : false
203
+ connection.select_value(relation, "#{name} Exists") ? true : false
204
204
  end
205
205
 
206
206
  protected
@@ -208,7 +208,7 @@ module ActiveRecord
208
208
  def find_with_associations
209
209
  join_dependency = construct_join_dependency_for_association_find
210
210
  relation = construct_relation_for_association_find(join_dependency)
211
- rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
211
+ rows = connection.select_all(relation, 'SQL', relation.bind_values)
212
212
  join_dependency.instantiate(rows)
213
213
  rescue ThrowResult
214
214
  []
@@ -265,6 +265,7 @@ module ActiveRecord
265
265
  if match.bang? && result.blank?
266
266
  raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
267
267
  else
268
+ yield(result) if block_given?
268
269
  result
269
270
  end
270
271
  end
@@ -1,21 +1,16 @@
1
1
  module ActiveRecord
2
2
  class PredicateBuilder # :nodoc:
3
- def self.build_from_hash(engine, attributes, default_table, allow_table_name = true)
3
+ def self.build_from_hash(engine, attributes, default_table)
4
4
  predicates = attributes.map do |column, value|
5
5
  table = default_table
6
6
 
7
- if allow_table_name && value.is_a?(Hash)
7
+ if value.is_a?(Hash)
8
8
  table = Arel::Table.new(column, engine)
9
-
10
- if value.empty?
11
- '1 = 2'
12
- else
13
- build_from_hash(engine, value, table, false)
14
- end
9
+ build_from_hash(engine, value, table)
15
10
  else
16
11
  column = column.to_s
17
12
 
18
- if allow_table_name && column.include?('.')
13
+ if column.include?('.')
19
14
  table_name, column = column.split('.', 2)
20
15
  table = Arel::Table.new(table_name, engine)
21
16
  end
@@ -27,21 +22,23 @@ module ActiveRecord
27
22
  value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
28
23
  attribute.in(value.arel.ast)
29
24
  when Array, ActiveRecord::Associations::CollectionProxy
30
- values = value.to_a.map { |x|
31
- x.is_a?(ActiveRecord::Base) ? x.id : x
32
- }
25
+ values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
26
+ ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
27
+
28
+ array_predicates = ranges.map {|range| attribute.in(range)}
33
29
 
34
30
  if values.include?(nil)
35
31
  values = values.compact
36
32
  if values.empty?
37
- attribute.eq nil
33
+ array_predicates << attribute.eq(nil)
38
34
  else
39
- attribute.in(values.compact).or attribute.eq(nil)
35
+ array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
40
36
  end
41
37
  else
42
- attribute.in(values)
38
+ array_predicates << attribute.in(values)
43
39
  end
44
40
 
41
+ array_predicates.inject {|composite, predicate| composite.or(predicate)}
45
42
  when Range, Arel::Relation
46
43
  attribute.in(value)
47
44
  when ActiveRecord::Base
@@ -49,10 +46,6 @@ module ActiveRecord
49
46
  when Class
50
47
  # FIXME: I think we need to deprecate this behavior
51
48
  attribute.eq(value.name)
52
- when Integer, ActiveSupport::Duration
53
- # Arel treats integers as literals, but they should be quoted when compared with strings
54
- column = engine.connection_pool.columns_hash[table.name][attribute.name.to_s]
55
- attribute.eq(Arel::Nodes::SqlLiteral.new(engine.connection.quote(value, column)))
56
49
  else
57
50
  attribute.eq(value)
58
51
  end
@@ -9,7 +9,8 @@ module ActiveRecord
9
9
  :select_values, :group_values, :order_values, :joins_values,
10
10
  :where_values, :having_values, :bind_values,
11
11
  :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
12
- :from_value, :reorder_value, :reverse_order_value
12
+ :from_value, :reordering_value, :reverse_order_value,
13
+ :uniq_value
13
14
 
14
15
  def includes(*args)
15
16
  args.reject! {|a| a.blank? }
@@ -38,7 +39,7 @@ module ActiveRecord
38
39
  end
39
40
 
40
41
  # Works in two unique ways.
41
- #
42
+ #
42
43
  # First: takes a block so it can be used just like Array#select.
43
44
  #
44
45
  # Model.scoped.select { |m| m.field == value }
@@ -56,16 +57,16 @@ module ActiveRecord
56
57
  # array, it actually returns a relation object and can have other query
57
58
  # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
58
59
  #
59
- # This method will also take multiple parameters:
60
+ # The argument to the method can also be an array of fields.
60
61
  #
61
- # >> Model.select(:field, :other_field, :and_one_more)
62
+ # >> Model.select([:field, :other_field, :and_one_more])
62
63
  # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
63
64
  #
64
65
  # Any attributes that do not have fields retrieved by a select
65
- # will return `nil` when the getter method for that attribute is used:
66
+ # will raise a ActiveModel::MissingAttributeError when the getter method for that attribute is used:
66
67
  #
67
68
  # >> Model.select(:field).first.other_field
68
- # => nil
69
+ # => ActiveModel::MissingAttributeError: missing attribute: other_field
69
70
  def select(value = Proc.new)
70
71
  if block_given?
71
72
  to_a.select {|*block_args| value.call(*block_args) }
@@ -92,11 +93,22 @@ module ActiveRecord
92
93
  relation
93
94
  end
94
95
 
96
+ # Replaces any existing order defined on the relation with the specified order.
97
+ #
98
+ # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
99
+ #
100
+ # Subsequent calls to order on the same relation will be appended. For example:
101
+ #
102
+ # User.order('email DESC').reorder('id ASC').order('name ASC')
103
+ #
104
+ # generates a query with 'ORDER BY id ASC, name ASC'.
105
+ #
95
106
  def reorder(*args)
96
107
  return self if args.blank?
97
108
 
98
109
  relation = clone
99
- relation.reorder_value = args.flatten
110
+ relation.reordering_value = true
111
+ relation.order_values = args.flatten
100
112
  relation
101
113
  end
102
114
 
@@ -176,6 +188,58 @@ module ActiveRecord
176
188
  relation
177
189
  end
178
190
 
191
+ # Specifies whether the records should be unique or not. For example:
192
+ #
193
+ # User.select(:name)
194
+ # # => Might return two records with the same name
195
+ #
196
+ # User.select(:name).uniq
197
+ # # => Returns 1 record per unique name
198
+ #
199
+ # User.select(:name).uniq.uniq(false)
200
+ # # => You can also remove the uniqueness
201
+ def uniq(value = true)
202
+ relation = clone
203
+ relation.uniq_value = value
204
+ relation
205
+ end
206
+
207
+ # Used to extend a scope with additional methods, either through
208
+ # a module or through a block provided.
209
+ #
210
+ # The object returned is a relation, which can be further extended.
211
+ #
212
+ # === Using a module
213
+ #
214
+ # module Pagination
215
+ # def page(number)
216
+ # # pagination code goes here
217
+ # end
218
+ # end
219
+ #
220
+ # scope = Model.scoped.extending(Pagination)
221
+ # scope.page(params[:page])
222
+ #
223
+ # You can also pass a list of modules:
224
+ #
225
+ # scope = Model.scoped.extending(Pagination, SomethingElse)
226
+ #
227
+ # === Using a block
228
+ #
229
+ # scope = Model.scoped.extending do
230
+ # def page(number)
231
+ # # pagination code goes here
232
+ # end
233
+ # end
234
+ # scope.page(params[:page])
235
+ #
236
+ # You can also use a block and a module list:
237
+ #
238
+ # scope = Model.scoped.extending(Pagination) do
239
+ # def per_page(number)
240
+ # # pagination code goes here
241
+ # end
242
+ # end
179
243
  def extending(*modules)
180
244
  modules << Module.new(&Proc.new) if block_given?
181
245
 
@@ -206,16 +270,17 @@ module ActiveRecord
206
270
  arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
207
271
 
208
272
  arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
209
- arel.skip(@offset_value.to_i) if @offset_value
273
+ arel.skip(@offset_value) if @offset_value
210
274
 
211
275
  arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
212
276
 
213
- order = @reorder_value ? @reorder_value : @order_values
277
+ order = @order_values
214
278
  order = reverse_sql_order(order) if @reverse_order_value
215
279
  arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
216
280
 
217
281
  build_select(arel, @select_values.uniq)
218
282
 
283
+ arel.distinct(@uniq_value)
219
284
  arel.from(@from_value) if @from_value
220
285
  arel.lock(@lock_value) if @lock_value
221
286
 
@@ -331,10 +396,11 @@ module ActiveRecord
331
396
 
332
397
  order_query.map do |o|
333
398
  case o
334
- when Arel::Nodes::Ascending, Arel::Nodes::Descending
399
+ when Arel::Nodes::Ordering
335
400
  o.reverse
336
401
  when String, Symbol
337
402
  o.to_s.split(',').collect do |s|
403
+ s.strip!
338
404
  s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
339
405
  end
340
406
  else
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  end
23
23
  end
24
24
 
25
- (Relation::MULTI_VALUE_METHODS - [:joins, :where]).each do |method|
25
+ (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method|
26
26
  value = r.send(:"#{method}_values")
27
27
  merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
28
28
  end
@@ -48,7 +48,7 @@ module ActiveRecord
48
48
 
49
49
  merged_relation.where_values = merged_wheres
50
50
 
51
- (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with]).each do |method|
51
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
52
52
  value = r.send(:"#{method}_value")
53
53
  merged_relation.send(:"#{method}_value=", value) unless value.nil?
54
54
  end
@@ -57,6 +57,15 @@ module ActiveRecord
57
57
 
58
58
  merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
59
59
 
60
+ if (r.reordering_value)
61
+ # override any order specified in the original relation
62
+ merged_relation.reordering_value = true
63
+ merged_relation.order_values = r.order_values
64
+ else
65
+ # merge in order_values from r
66
+ merged_relation.order_values += r.order_values
67
+ end
68
+
60
69
  # Apply scope extension modules
61
70
  merged_relation.send :apply_modules, r.extensions
62
71
 
@@ -1,5 +1,5 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/module/delegation'
3
3
 
4
4
  module ActiveRecord
5
5
  # = Active Record Relation
@@ -7,13 +7,9 @@ module ActiveRecord
7
7
  JoinOperation = Struct.new(:relation, :join_class, :on)
8
8
  ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
9
9
  MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
10
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order]
10
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
11
11
 
12
- include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
13
-
14
- # These are explicitly delegated to improve performance (avoids method_missing)
15
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
16
- delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
12
+ include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
17
13
 
18
14
  attr_reader :table, :klass, :loaded
19
15
  attr_accessor :extensions, :default_scoped
@@ -81,7 +77,6 @@ module ActiveRecord
81
77
  end
82
78
 
83
79
  def initialize_copy(other)
84
- @bind_values = @bind_values.dup
85
80
  reset
86
81
  end
87
82
 
@@ -95,14 +90,77 @@ module ActiveRecord
95
90
  scoping { @klass.create!(*args, &block) }
96
91
  end
97
92
 
98
- def respond_to?(method, include_private = false)
99
- arel.respond_to?(method, include_private) ||
100
- Array.method_defined?(method) ||
101
- @klass.respond_to?(method, include_private) ||
102
- super
93
+ # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
94
+ #
95
+ # Expects arguments in the same format as <tt>Base.create</tt>.
96
+ #
97
+ # ==== Examples
98
+ # # Find the first user named Penélope or create a new one.
99
+ # User.where(:first_name => 'Penélope').first_or_create
100
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
101
+ #
102
+ # # Find the first user named Penélope or create a new one.
103
+ # # We already have one so the existing record will be returned.
104
+ # User.where(:first_name => 'Penélope').first_or_create
105
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
106
+ #
107
+ # # Find the first user named Scarlett or create a new one with a particular last name.
108
+ # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
109
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
110
+ #
111
+ # # Find the first user named Scarlett or create a new one with a different last name.
112
+ # # We already have one so the existing record will be returned.
113
+ # User.where(:first_name => 'Scarlett').first_or_create do |user|
114
+ # user.last_name = "O'Hara"
115
+ # end
116
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
117
+ def first_or_create(attributes = nil, options = {}, &block)
118
+ first || create(attributes, options, &block)
119
+ end
120
+
121
+ # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
122
+ #
123
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
124
+ def first_or_create!(attributes = nil, options = {}, &block)
125
+ first || create!(attributes, options, &block)
126
+ end
127
+
128
+ # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
129
+ #
130
+ # Expects arguments in the same format as <tt>Base.new</tt>.
131
+ def first_or_initialize(attributes = nil, options = {}, &block)
132
+ first || new(attributes, options, &block)
133
+ end
134
+
135
+ # Runs EXPLAIN on the query or queries triggered by this relation and
136
+ # returns the result as a string. The string is formatted imitating the
137
+ # ones printed by the database shell.
138
+ #
139
+ # Note that this method actually runs the queries, since the results of some
140
+ # are needed by the next ones when eager loading is going on.
141
+ #
142
+ # Please see further details in the
143
+ # {Active Record Query Interface guide}[http://edgeguides.rubyonrails.org/active_record_querying.html#running-explain].
144
+ def explain
145
+ _, queries = collecting_queries_for_explain { exec_queries }
146
+ exec_explain(queries)
103
147
  end
104
148
 
105
149
  def to_a
150
+ # We monitor here the entire execution rather than individual SELECTs
151
+ # because from the point of view of the user fetching the records of a
152
+ # relation is a single unit of work. You want to know if this call takes
153
+ # too long, not if the individual queries take too long.
154
+ #
155
+ # It could be the case that none of the queries involved surpass the
156
+ # threshold, and at the same time the sum of them all does. The user
157
+ # should get a query plan logged in that case.
158
+ logging_query_plan do
159
+ exec_queries
160
+ end
161
+ end
162
+
163
+ def exec_queries
106
164
  return @records if loaded?
107
165
 
108
166
  default_scoped = with_default_scope
@@ -133,6 +191,7 @@ module ActiveRecord
133
191
  @loaded = true
134
192
  @records
135
193
  end
194
+ private :exec_queries
136
195
 
137
196
  def as_json(options = nil) #:nodoc:
138
197
  to_a.as_json(options)
@@ -178,7 +237,7 @@ module ActiveRecord
178
237
  # Please check unscoped if you want to remove all previous scopes (including
179
238
  # the default_scope) during the execution of a block.
180
239
  def scoping
181
- @klass.send(:with_scope, self, :overwrite) { yield }
240
+ @klass.with_scope(self, :overwrite) { yield }
182
241
  end
183
242
 
184
243
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -253,8 +312,7 @@ module ActiveRecord
253
312
  # Person.update(people.keys, people.values)
254
313
  def update(id, attributes)
255
314
  if id.is_a?(Array)
256
- idx = -1
257
- id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
315
+ id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
258
316
  else
259
317
  object = find(id)
260
318
  object.update_attributes(attributes)
@@ -298,7 +356,7 @@ module ActiveRecord
298
356
  end
299
357
 
300
358
  # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
301
- # therefore all callbacks and filters are fired off before the object is deleted. This method is
359
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
302
360
  # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
303
361
  #
304
362
  # This essentially finds the object (or multiple objects) with the given id, creates a new object
@@ -327,7 +385,7 @@ module ActiveRecord
327
385
  # Deletes the records matching +conditions+ without instantiating the records first, and hence not
328
386
  # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
329
387
  # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
330
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
388
+ # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
331
389
  # the number of rows affected.
332
390
  #
333
391
  # ==== Parameters
@@ -395,7 +453,7 @@ module ActiveRecord
395
453
  end
396
454
 
397
455
  def to_sql
398
- @to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
456
+ @to_sql ||= klass.connection.to_sql(arel)
399
457
  end
400
458
 
401
459
  def where_values_hash
@@ -447,20 +505,6 @@ module ActiveRecord
447
505
  end
448
506
  end
449
507
 
450
- protected
451
-
452
- def method_missing(method, *args, &block)
453
- if Array.method_defined?(method)
454
- to_a.send(method, *args, &block)
455
- elsif @klass.respond_to?(method)
456
- scoping { @klass.send(method, *args, &block) }
457
- elsif arel.respond_to?(method)
458
- arel.send(method, *args, &block)
459
- else
460
- super
461
- end
462
- end
463
-
464
508
  private
465
509
 
466
510
  def references_eager_loaded_tables?
@@ -486,6 +530,5 @@ module ActiveRecord
486
530
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
487
531
  string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
488
532
  end
489
-
490
533
  end
491
534
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  ###
3
3
  # This class encapsulates a Result returned from calling +exec_query+ on any
4
- # database connection adapter. For example:
4
+ # database connection adapter. For example:
5
5
  #
6
6
  # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
7
7
  # x # => #<ActiveRecord::Result:0xdeadbeef>