activerecord 3.1.9 → 3.2.12

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 (112) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +317 -336
  3. data/README.rdoc +3 -3
  4. data/examples/performance.rb +20 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +3 -6
  7. data/lib/active_record/associations/association.rb +3 -42
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/builder/association.rb +6 -4
  10. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  11. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +4 -4
  13. data/lib/active_record/associations/builder/has_one.rb +5 -6
  14. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  15. data/lib/active_record/associations/collection_association.rb +64 -31
  16. data/lib/active_record/associations/collection_proxy.rb +2 -37
  17. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  18. data/lib/active_record/associations/has_many_association.rb +5 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +28 -9
  20. data/lib/active_record/associations/has_one_association.rb +15 -13
  21. data/lib/active_record/associations/join_dependency.rb +2 -2
  22. data/lib/active_record/associations/preloader.rb +14 -10
  23. data/lib/active_record/associations/through_association.rb +7 -3
  24. data/lib/active_record/associations.rb +92 -76
  25. data/lib/active_record/attribute_assignment.rb +221 -0
  26. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  27. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  28. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  29. data/lib/active_record/attribute_methods/read.rb +73 -83
  30. data/lib/active_record/attribute_methods/serialization.rb +102 -0
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +23 -17
  32. data/lib/active_record/attribute_methods/write.rb +31 -6
  33. data/lib/active_record/attribute_methods.rb +231 -30
  34. data/lib/active_record/autosave_association.rb +43 -22
  35. data/lib/active_record/base.rb +227 -1708
  36. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  37. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  38. data/lib/active_record/connection_adapters/abstract/database_statements.rb +6 -33
  39. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  40. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -6
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -26
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +674 -0
  45. data/lib/active_record/connection_adapters/column.rb +37 -11
  46. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -581
  47. data/lib/active_record/connection_adapters/mysql_adapter.rb +137 -696
  48. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -86
  49. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  50. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  51. data/lib/active_record/connection_adapters/sqlite_adapter.rb +55 -32
  52. data/lib/active_record/counter_cache.rb +9 -4
  53. data/lib/active_record/dynamic_finder_match.rb +12 -0
  54. data/lib/active_record/dynamic_matchers.rb +84 -0
  55. data/lib/active_record/errors.rb +11 -1
  56. data/lib/active_record/explain.rb +85 -0
  57. data/lib/active_record/explain_subscriber.rb +25 -0
  58. data/lib/active_record/fixtures/file.rb +65 -0
  59. data/lib/active_record/fixtures.rb +56 -85
  60. data/lib/active_record/identity_map.rb +3 -4
  61. data/lib/active_record/inheritance.rb +174 -0
  62. data/lib/active_record/integration.rb +49 -0
  63. data/lib/active_record/locking/optimistic.rb +30 -25
  64. data/lib/active_record/locking/pessimistic.rb +23 -1
  65. data/lib/active_record/log_subscriber.rb +3 -3
  66. data/lib/active_record/migration/command_recorder.rb +8 -8
  67. data/lib/active_record/migration.rb +68 -35
  68. data/lib/active_record/model_schema.rb +366 -0
  69. data/lib/active_record/nested_attributes.rb +3 -2
  70. data/lib/active_record/persistence.rb +57 -11
  71. data/lib/active_record/querying.rb +58 -0
  72. data/lib/active_record/railtie.rb +31 -29
  73. data/lib/active_record/railties/controller_runtime.rb +3 -1
  74. data/lib/active_record/railties/databases.rake +191 -110
  75. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  76. data/lib/active_record/readonly_attributes.rb +26 -0
  77. data/lib/active_record/reflection.rb +7 -15
  78. data/lib/active_record/relation/batches.rb +5 -2
  79. data/lib/active_record/relation/calculations.rb +47 -15
  80. data/lib/active_record/relation/delegation.rb +49 -0
  81. data/lib/active_record/relation/finder_methods.rb +9 -7
  82. data/lib/active_record/relation/predicate_builder.rb +18 -7
  83. data/lib/active_record/relation/query_methods.rb +75 -9
  84. data/lib/active_record/relation/spawn_methods.rb +11 -2
  85. data/lib/active_record/relation.rb +78 -32
  86. data/lib/active_record/result.rb +1 -1
  87. data/lib/active_record/sanitization.rb +194 -0
  88. data/lib/active_record/schema_dumper.rb +12 -5
  89. data/lib/active_record/scoping/default.rb +142 -0
  90. data/lib/active_record/scoping/named.rb +202 -0
  91. data/lib/active_record/scoping.rb +152 -0
  92. data/lib/active_record/serialization.rb +1 -43
  93. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  94. data/lib/active_record/session_store.rb +17 -15
  95. data/lib/active_record/store.rb +52 -0
  96. data/lib/active_record/test_case.rb +11 -7
  97. data/lib/active_record/timestamp.rb +17 -3
  98. data/lib/active_record/transactions.rb +27 -6
  99. data/lib/active_record/translation.rb +22 -0
  100. data/lib/active_record/validations/associated.rb +5 -4
  101. data/lib/active_record/validations/uniqueness.rb +7 -7
  102. data/lib/active_record/validations.rb +1 -1
  103. data/lib/active_record/version.rb +2 -2
  104. data/lib/active_record.rb +38 -3
  105. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  106. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  107. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  108. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  109. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  110. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  111. metadata +30 -10
  112. data/lib/active_record/named_scope.rb +0 -200
@@ -66,7 +66,7 @@ module ActiveRecord
66
66
  calculate(:average, column_name, options)
67
67
  end
68
68
 
69
- # Calculates the minimum value on a given column. The value is returned
69
+ # Calculates the minimum value on a given column. The value is returned
70
70
  # with the same data type of the column, or +nil+ if there's no row. See
71
71
  # +calculate+ for examples with options.
72
72
  #
@@ -89,11 +89,15 @@ module ActiveRecord
89
89
  # +calculate+ for examples with options.
90
90
  #
91
91
  # Person.sum('age') # => 4562
92
- def sum(column_name, options = {})
93
- calculate(:sum, column_name, options)
92
+ def sum(*args)
93
+ if block_given?
94
+ self.to_a.sum(*args) {|*block_args| yield(*block_args)}
95
+ else
96
+ calculate(:sum, *args)
97
+ end
94
98
  end
95
99
 
96
- # This calculates aggregate values in the given column. Methods for count, sum, average,
100
+ # This calculates aggregate values in the given column. Methods for count, sum, average,
97
101
  # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
98
102
  # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
99
103
  #
@@ -101,7 +105,7 @@ module ActiveRecord
101
105
  # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
102
106
  # for AVG, and the given column's type for everything else.
103
107
  # * Grouped values: This returns an ordered hash of the values and groups them by the
104
- # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
108
+ # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
105
109
  #
106
110
  # values = Person.maximum(:age, :group => 'last_name')
107
111
  # puts values["Drake"]
@@ -119,7 +123,7 @@ module ActiveRecord
119
123
  # Options:
120
124
  # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
121
125
  # See conditions in the intro to ActiveRecord::Base.
122
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
126
+ # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
123
127
  # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
124
128
  # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
125
129
  # (Rarely needed).
@@ -162,6 +166,23 @@ module ActiveRecord
162
166
  0
163
167
  end
164
168
 
169
+ # This method is designed to perform select by a single column as direct SQL query
170
+ # Returns <tt>Array</tt> with values of the specified column name
171
+ # The values has same data type as column.
172
+ #
173
+ # Examples:
174
+ #
175
+ # Person.pluck(:id) # SELECT people.id FROM people
176
+ # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
177
+ # Person.where(:confirmed => true).limit(5).pluck(:id)
178
+ #
179
+ def pluck(column_name)
180
+ column_name = column_name.to_s
181
+ klass.connection.select_all(select(column_name).arel).map! do |attributes|
182
+ klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes))
183
+ end
184
+ end
185
+
165
186
  private
166
187
 
167
188
  def perform_calculation(operation, column_name, options = {})
@@ -223,10 +244,16 @@ module ActiveRecord
223
244
  end
224
245
 
225
246
  def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
226
- group_attr = @group_values
227
- association = @klass.reflect_on_association(group_attr.first.to_sym)
228
- associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
229
- group_fields = Array(associated ? association.foreign_key : group_attr)
247
+ group_attrs = @group_values
248
+
249
+ if group_attrs.first.respond_to?(:to_sym)
250
+ association = @klass.reflect_on_association(group_attrs.first.to_sym)
251
+ associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
252
+ group_fields = Array(associated ? association.foreign_key : group_attrs)
253
+ else
254
+ group_fields = group_attrs
255
+ end
256
+
230
257
  group_aliases = group_fields.map { |field| column_alias_for(field) }
231
258
  group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
232
259
  [aliaz, column_for(field)]
@@ -249,10 +276,14 @@ module ActiveRecord
249
276
  select_values += @select_values unless @having_values.empty?
250
277
 
251
278
  select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
252
- "#{field} AS #{aliaz}"
279
+ if field.respond_to?(:as)
280
+ field.as(aliaz)
281
+ else
282
+ "#{field} AS #{aliaz}"
283
+ end
253
284
  }
254
285
 
255
- relation = except(:group).group(group.join(','))
286
+ relation = except(:group).group(group)
256
287
  relation.select_values = select_values
257
288
 
258
289
  calculated_data = @klass.connection.select_all(relation)
@@ -264,10 +295,10 @@ module ActiveRecord
264
295
  end
265
296
 
266
297
  ActiveSupport::OrderedHash[calculated_data.map do |row|
267
- key = group_columns.map { |aliaz, column|
298
+ key = group_columns.map { |aliaz, column|
268
299
  type_cast_calculated_value(row[aliaz], column)
269
300
  }
270
- key = key.first if key.size == 1
301
+ key = key.first if key.size == 1
271
302
  key = key_records[key] if associated
272
303
  [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
273
304
  end]
@@ -282,6 +313,7 @@ module ActiveRecord
282
313
  # column_alias_for("count(*)") # => "count_all"
283
314
  # column_alias_for("count", "id") # => "count_id"
284
315
  def column_alias_for(*keys)
316
+ keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k}
285
317
  table_name = keys.join(' ')
286
318
  table_name.downcase!
287
319
  table_name.gsub!(/\*/, 'all')
@@ -293,7 +325,7 @@ module ActiveRecord
293
325
  end
294
326
 
295
327
  def column_for(field)
296
- field_name = field.to_s.split('.').last
328
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
297
329
  @klass.columns.detect { |c| c.name.to_s == field_name }
298
330
  end
299
331
 
@@ -0,0 +1,49 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module ActiveRecord
4
+ module Delegation
5
+ # Set up common delegations for performance (avoids method_missing)
6
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
7
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
8
+ :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
9
+
10
+ def self.delegate_to_scoped_klass(method)
11
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
12
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
13
+ def #{method}(*args, &block)
14
+ scoping { @klass.#{method}(*args, &block) }
15
+ end
16
+ RUBY
17
+ else
18
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
19
+ def #{method}(*args, &block)
20
+ scoping { @klass.send(#{method.inspect}, *args, &block) }
21
+ end
22
+ RUBY
23
+ end
24
+ end
25
+
26
+ def respond_to?(method, include_private = false)
27
+ super || Array.method_defined?(method) ||
28
+ @klass.respond_to?(method, include_private) ||
29
+ arel.respond_to?(method, include_private)
30
+ end
31
+
32
+ protected
33
+
34
+ def method_missing(method, *args, &block)
35
+ if @klass.respond_to?(method)
36
+ ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
37
+ scoping { @klass.send(method, *args, &block) }
38
+ elsif Array.method_defined?(method)
39
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
40
+ to_a.send(method, *args, &block)
41
+ elsif arel.respond_to?(method)
42
+ ::ActiveRecord::Delegation.delegate method, :to => :arel
43
+ arel.send(method, *args, &block)
44
+ else
45
+ super
46
+ end
47
+ end
48
+ end
49
+ end
@@ -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)
@@ -185,13 +185,12 @@ module ActiveRecord
185
185
  # Person.exists?(['name LIKE ?', "%#{query}%"])
186
186
  # Person.exists?
187
187
  def exists?(id = false)
188
- return false if id.nil?
189
-
190
188
  id = id.id if ActiveRecord::Base === id
189
+ return false if id.nil?
191
190
 
192
191
  join_dependency = construct_join_dependency_for_association_find
193
192
  relation = construct_relation_for_association_find(join_dependency)
194
- relation = relation.except(:select).select("1").limit(1)
193
+ relation = relation.except(:select, :order).select("1 AS one").limit(1)
195
194
 
196
195
  case id
197
196
  when Array, Hash
@@ -200,7 +199,9 @@ module ActiveRecord
200
199
  relation = relation.where(table[primary_key].eq(id)) if id
201
200
  end
202
201
 
203
- connection.select_value(relation) ? true : false
202
+ connection.select_value(relation, "#{name} Exists") ? true : false
203
+ rescue ThrowResult
204
+ false
204
205
  end
205
206
 
206
207
  protected
@@ -265,6 +266,7 @@ module ActiveRecord
265
266
  if match.bang? && result.blank?
266
267
  raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
267
268
  else
269
+ yield(result) if block_given?
268
270
  result
269
271
  end
270
272
  end
@@ -289,7 +291,7 @@ module ActiveRecord
289
291
  r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
290
292
  end
291
293
  yield(record) if block_given?
292
- record.save if match.instantiator == :create
294
+ record.send(match.save_method) if match.save_record?
293
295
  end
294
296
 
295
297
  record
@@ -6,7 +6,12 @@ module ActiveRecord
6
6
 
7
7
  if allow_table_name && value.is_a?(Hash)
8
8
  table = Arel::Table.new(column, engine)
9
- build_from_hash(engine, value, table, false)
9
+
10
+ if value.empty?
11
+ '1 = 2'
12
+ else
13
+ build_from_hash(engine, value, table, false)
14
+ end
10
15
  else
11
16
  column = column.to_s
12
17
 
@@ -22,21 +27,23 @@ module ActiveRecord
22
27
  value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
23
28
  attribute.in(value.arel.ast)
24
29
  when Array, ActiveRecord::Associations::CollectionProxy
25
- values = value.to_a.map { |x|
26
- x.is_a?(ActiveRecord::Base) ? x.id : x
27
- }
30
+ values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
31
+ ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
32
+
33
+ array_predicates = ranges.map {|range| attribute.in(range)}
28
34
 
29
35
  if values.include?(nil)
30
36
  values = values.compact
31
37
  if values.empty?
32
- attribute.eq nil
38
+ array_predicates << attribute.eq(nil)
33
39
  else
34
- attribute.in(values.compact).or attribute.eq(nil)
40
+ array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
35
41
  end
36
42
  else
37
- attribute.in(values)
43
+ array_predicates << attribute.in(values)
38
44
  end
39
45
 
46
+ array_predicates.inject {|composite, predicate| composite.or(predicate)}
40
47
  when Range, Arel::Relation
41
48
  attribute.in(value)
42
49
  when ActiveRecord::Base
@@ -44,6 +51,10 @@ module ActiveRecord
44
51
  when Class
45
52
  # FIXME: I think we need to deprecate this behavior
46
53
  attribute.eq(value.name)
54
+ when Integer, ActiveSupport::Duration
55
+ # Arel treats integers as literals, but they should be quoted when compared with strings
56
+ column = engine.connection.schema_cache.columns_hash[table.name][attribute.name.to_s]
57
+ attribute.eq(Arel::Nodes::SqlLiteral.new(engine.connection.quote(value, column)))
47
58
  else
48
59
  attribute.eq(value)
49
60
  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
 
@@ -210,12 +274,13 @@ module ActiveRecord
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
@@ -95,14 +91,77 @@ module ActiveRecord
95
91
  scoping { @klass.create!(*args, &block) }
96
92
  end
97
93
 
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
94
+ # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
95
+ #
96
+ # Expects arguments in the same format as <tt>Base.create</tt>.
97
+ #
98
+ # ==== Examples
99
+ # # Find the first user named Penélope or create a new one.
100
+ # User.where(:first_name => 'Penélope').first_or_create
101
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
102
+ #
103
+ # # Find the first user named Penélope or create a new one.
104
+ # # We already have one so the existing record will be returned.
105
+ # User.where(:first_name => 'Penélope').first_or_create
106
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
107
+ #
108
+ # # Find the first user named Scarlett or create a new one with a particular last name.
109
+ # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
110
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
111
+ #
112
+ # # Find the first user named Scarlett or create a new one with a different last name.
113
+ # # We already have one so the existing record will be returned.
114
+ # User.where(:first_name => 'Scarlett').first_or_create do |user|
115
+ # user.last_name = "O'Hara"
116
+ # end
117
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
118
+ def first_or_create(attributes = nil, options = {}, &block)
119
+ first || create(attributes, options, &block)
120
+ end
121
+
122
+ # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
123
+ #
124
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
125
+ def first_or_create!(attributes = nil, options = {}, &block)
126
+ first || create!(attributes, options, &block)
127
+ end
128
+
129
+ # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
130
+ #
131
+ # Expects arguments in the same format as <tt>Base.new</tt>.
132
+ def first_or_initialize(attributes = nil, options = {}, &block)
133
+ first || new(attributes, options, &block)
134
+ end
135
+
136
+ # Runs EXPLAIN on the query or queries triggered by this relation and
137
+ # returns the result as a string. The string is formatted imitating the
138
+ # ones printed by the database shell.
139
+ #
140
+ # Note that this method actually runs the queries, since the results of some
141
+ # are needed by the next ones when eager loading is going on.
142
+ #
143
+ # Please see further details in the
144
+ # {Active Record Query Interface guide}[http://edgeguides.rubyonrails.org/active_record_querying.html#running-explain].
145
+ def explain
146
+ _, queries = collecting_queries_for_explain { exec_queries }
147
+ exec_explain(queries)
103
148
  end
104
149
 
105
150
  def to_a
151
+ # We monitor here the entire execution rather than individual SELECTs
152
+ # because from the point of view of the user fetching the records of a
153
+ # relation is a single unit of work. You want to know if this call takes
154
+ # too long, not if the individual queries take too long.
155
+ #
156
+ # It could be the case that none of the queries involved surpass the
157
+ # threshold, and at the same time the sum of them all does. The user
158
+ # should get a query plan logged in that case.
159
+ logging_query_plan do
160
+ exec_queries
161
+ end
162
+ end
163
+
164
+ def exec_queries
106
165
  return @records if loaded?
107
166
 
108
167
  default_scoped = with_default_scope
@@ -133,6 +192,7 @@ module ActiveRecord
133
192
  @loaded = true
134
193
  @records
135
194
  end
195
+ private :exec_queries
136
196
 
137
197
  def as_json(options = nil) #:nodoc:
138
198
  to_a.as_json(options)
@@ -178,7 +238,7 @@ module ActiveRecord
178
238
  # Please check unscoped if you want to remove all previous scopes (including
179
239
  # the default_scope) during the execution of a block.
180
240
  def scoping
181
- @klass.send(:with_scope, self, :overwrite) { yield }
241
+ @klass.with_scope(self, :overwrite) { yield }
182
242
  end
183
243
 
184
244
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -253,8 +313,7 @@ module ActiveRecord
253
313
  # Person.update(people.keys, people.values)
254
314
  def update(id, attributes)
255
315
  if id.is_a?(Array)
256
- idx = -1
257
- id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
316
+ id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
258
317
  else
259
318
  object = find(id)
260
319
  object.update_attributes(attributes)
@@ -298,7 +357,7 @@ module ActiveRecord
298
357
  end
299
358
 
300
359
  # 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
360
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
302
361
  # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
303
362
  #
304
363
  # This essentially finds the object (or multiple objects) with the given id, creates a new object
@@ -327,7 +386,7 @@ module ActiveRecord
327
386
  # Deletes the records matching +conditions+ without instantiating the records first, and hence not
328
387
  # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
329
388
  # 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
389
+ # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
331
390
  # the number of rows affected.
332
391
  #
333
392
  # ==== Parameters
@@ -344,6 +403,8 @@ module ActiveRecord
344
403
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
345
404
  # +after_destroy+ callbacks, use the +destroy_all+ method instead.
346
405
  def delete_all(conditions = nil)
406
+ raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
407
+
347
408
  IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
348
409
  if conditions
349
410
  where(conditions).delete_all
@@ -447,20 +508,6 @@ module ActiveRecord
447
508
  end
448
509
  end
449
510
 
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
511
  private
465
512
 
466
513
  def references_eager_loaded_tables?
@@ -486,6 +533,5 @@ module ActiveRecord
486
533
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
487
534
  string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
488
535
  end
489
-
490
536
  end
491
537
  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>