activerecord 3.1.12 → 3.2.22.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +804 -338
  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 +13 -45
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
  11. data/lib/active_record/associations/builder/association.rb +6 -4
  12. data/lib/active_record/associations/builder/belongs_to.rb +7 -4
  13. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +4 -4
  15. data/lib/active_record/associations/builder/has_one.rb +5 -6
  16. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  17. data/lib/active_record/associations/collection_association.rb +65 -32
  18. data/lib/active_record/associations/collection_proxy.rb +8 -41
  19. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  20. data/lib/active_record/associations/has_many_association.rb +11 -7
  21. data/lib/active_record/associations/has_many_through_association.rb +19 -9
  22. data/lib/active_record/associations/has_one_association.rb +23 -13
  23. data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
  24. data/lib/active_record/associations/join_dependency.rb +3 -3
  25. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  26. data/lib/active_record/associations/preloader.rb +14 -10
  27. data/lib/active_record/associations/through_association.rb +8 -4
  28. data/lib/active_record/associations.rb +92 -76
  29. data/lib/active_record/attribute_assignment.rb +221 -0
  30. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  32. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  33. data/lib/active_record/attribute_methods/read.rb +73 -83
  34. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  36. data/lib/active_record/attribute_methods/write.rb +32 -6
  37. data/lib/active_record/attribute_methods.rb +231 -30
  38. data/lib/active_record/autosave_association.rb +44 -26
  39. data/lib/active_record/base.rb +227 -1708
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  41. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  45. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  49. data/lib/active_record/connection_adapters/column.rb +37 -11
  50. data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
  51. data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
  52. data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
  53. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  54. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  55. data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
  56. data/lib/active_record/counter_cache.rb +9 -4
  57. data/lib/active_record/dynamic_finder_match.rb +12 -0
  58. data/lib/active_record/dynamic_matchers.rb +84 -0
  59. data/lib/active_record/errors.rb +11 -1
  60. data/lib/active_record/explain.rb +86 -0
  61. data/lib/active_record/explain_subscriber.rb +25 -0
  62. data/lib/active_record/fixtures/file.rb +65 -0
  63. data/lib/active_record/fixtures.rb +57 -86
  64. data/lib/active_record/identity_map.rb +3 -4
  65. data/lib/active_record/inheritance.rb +174 -0
  66. data/lib/active_record/integration.rb +60 -0
  67. data/lib/active_record/locking/optimistic.rb +33 -26
  68. data/lib/active_record/locking/pessimistic.rb +23 -1
  69. data/lib/active_record/log_subscriber.rb +8 -4
  70. data/lib/active_record/migration/command_recorder.rb +8 -8
  71. data/lib/active_record/migration.rb +68 -35
  72. data/lib/active_record/model_schema.rb +368 -0
  73. data/lib/active_record/nested_attributes.rb +60 -24
  74. data/lib/active_record/persistence.rb +57 -11
  75. data/lib/active_record/query_cache.rb +6 -6
  76. data/lib/active_record/querying.rb +58 -0
  77. data/lib/active_record/railtie.rb +37 -29
  78. data/lib/active_record/railties/controller_runtime.rb +3 -1
  79. data/lib/active_record/railties/databases.rake +213 -117
  80. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  81. data/lib/active_record/readonly_attributes.rb +26 -0
  82. data/lib/active_record/reflection.rb +7 -15
  83. data/lib/active_record/relation/batches.rb +7 -4
  84. data/lib/active_record/relation/calculations.rb +55 -16
  85. data/lib/active_record/relation/delegation.rb +49 -0
  86. data/lib/active_record/relation/finder_methods.rb +16 -11
  87. data/lib/active_record/relation/predicate_builder.rb +8 -6
  88. data/lib/active_record/relation/query_methods.rb +75 -9
  89. data/lib/active_record/relation/spawn_methods.rb +48 -7
  90. data/lib/active_record/relation.rb +78 -32
  91. data/lib/active_record/result.rb +10 -4
  92. data/lib/active_record/sanitization.rb +194 -0
  93. data/lib/active_record/schema_dumper.rb +12 -5
  94. data/lib/active_record/scoping/default.rb +142 -0
  95. data/lib/active_record/scoping/named.rb +200 -0
  96. data/lib/active_record/scoping.rb +152 -0
  97. data/lib/active_record/serialization.rb +1 -43
  98. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  99. data/lib/active_record/session_store.rb +18 -16
  100. data/lib/active_record/store.rb +52 -0
  101. data/lib/active_record/test_case.rb +11 -7
  102. data/lib/active_record/timestamp.rb +17 -3
  103. data/lib/active_record/transactions.rb +27 -6
  104. data/lib/active_record/translation.rb +22 -0
  105. data/lib/active_record/validations/associated.rb +5 -4
  106. data/lib/active_record/validations/uniqueness.rb +8 -8
  107. data/lib/active_record/validations.rb +1 -1
  108. data/lib/active_record/version.rb +3 -3
  109. data/lib/active_record.rb +38 -3
  110. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  111. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  112. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  113. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  114. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  115. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  116. metadata +49 -28
  117. data/lib/active_record/named_scope.rb +0 -200
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/module/deprecation'
3
2
  require 'active_support/core_ext/object/inclusion'
4
3
 
5
4
  module ActiveRecord
@@ -125,7 +124,7 @@ module ActiveRecord
125
124
  # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
126
125
  # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
127
126
  def class_name
128
- @class_name ||= options[:class_name] || derive_class_name
127
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
129
128
  end
130
129
 
131
130
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -178,15 +177,9 @@ module ActiveRecord
178
177
  @collection = macro.in?([:has_many, :has_and_belongs_to_many])
179
178
  end
180
179
 
181
- # This is a hack so that we can tell if build_association was overridden, in order to
182
- # provide an appropriate deprecation if the overridden method ignored the &block. Please
183
- # see Association#build_record for details.
184
- attr_accessor :original_build_association_called # :nodoc
185
-
186
180
  # Returns a new, unsaved instance of the associated class. +options+ will
187
181
  # be passed to the class's constructor.
188
182
  def build_association(*options, &block)
189
- @original_build_association_called = true
190
183
  klass.new(*options, &block)
191
184
  end
192
185
 
@@ -202,11 +195,6 @@ module ActiveRecord
202
195
  @foreign_key ||= options[:foreign_key] || derive_foreign_key
203
196
  end
204
197
 
205
- def primary_key_name
206
- foreign_key
207
- end
208
- deprecate :primary_key_name => :foreign_key
209
-
210
198
  def foreign_type
211
199
  @foreign_type ||= options[:foreign_type] || "#{name}_type"
212
200
  end
@@ -274,6 +262,10 @@ module ActiveRecord
274
262
  [self]
275
263
  end
276
264
 
265
+ def nested?
266
+ false
267
+ end
268
+
277
269
  # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
278
270
  # in the #chain. The inside arrays are simply conditions (and each condition may itself be
279
271
  # a hash, array, arel predicate, etc...)
@@ -381,7 +373,7 @@ module ActiveRecord
381
373
  delegate :foreign_key, :foreign_type, :association_foreign_key,
382
374
  :active_record_primary_key, :type, :to => :source_reflection
383
375
 
384
- # Gets the source of the through reflection. It checks both a singularized
376
+ # Gets the source of the through reflection. It checks both a singularized
385
377
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
386
378
  #
387
379
  # class Post < ActiveRecord::Base
@@ -469,7 +461,7 @@ module ActiveRecord
469
461
  source_reflection.source_macro
470
462
  end
471
463
 
472
- # A through association is nested iff there would be more than one join table
464
+ # A through association is nested if there would be more than one join table
473
465
  def nested?
474
466
  chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
475
467
  end
@@ -59,18 +59,21 @@ module ActiveRecord
59
59
  relation = apply_finder_options(finder_options)
60
60
  end
61
61
 
62
- start = options.delete(:start).to_i
62
+ start = options.delete(:start)
63
63
  batch_size = options.delete(:batch_size) || 1000
64
64
 
65
65
  relation = relation.reorder(batch_order).limit(batch_size)
66
- records = relation.where(table[primary_key].gteq(start)).all
66
+ records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
67
67
 
68
68
  while records.any?
69
+ records_size = records.size
70
+ primary_key_offset = records.last.id
71
+
69
72
  yield records
70
73
 
71
- break if records.size < batch_size
74
+ break if records_size < batch_size
72
75
 
73
- if primary_key_offset = records.last.id
76
+ if primary_key_offset
74
77
  records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
75
78
  else
76
79
  raise "Primary key not included in the custom select clause"
@@ -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,12 +166,36 @@ 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
+ if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
181
+ column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
182
+ end
183
+
184
+ result = klass.connection.exec_query(select(column_name).to_sql)
185
+ last_column = result.columns.last
186
+
187
+ result.map do |attributes|
188
+ klass.type_cast_attribute(last_column, klass.initialize_attributes(attributes))
189
+ end
190
+ end
191
+
165
192
  private
166
193
 
167
194
  def perform_calculation(operation, column_name, options = {})
168
195
  operation = operation.to_s.downcase
169
196
 
170
- distinct = options[:distinct]
197
+ # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count)
198
+ distinct = options[:distinct] || self.uniq_value
171
199
 
172
200
  if operation == "count"
173
201
  column_name ||= (select_for_count || :all)
@@ -223,10 +251,16 @@ module ActiveRecord
223
251
  end
224
252
 
225
253
  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)
254
+ group_attrs = @group_values
255
+
256
+ if group_attrs.first.respond_to?(:to_sym)
257
+ association = @klass.reflect_on_association(group_attrs.first.to_sym)
258
+ associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
259
+ group_fields = Array(associated ? association.foreign_key : group_attrs)
260
+ else
261
+ group_fields = group_attrs
262
+ end
263
+
230
264
  group_aliases = group_fields.map { |field| column_alias_for(field) }
231
265
  group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
232
266
  [aliaz, column_for(field)]
@@ -249,10 +283,14 @@ module ActiveRecord
249
283
  select_values += @select_values unless @having_values.empty?
250
284
 
251
285
  select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
252
- "#{field} AS #{aliaz}"
286
+ if field.respond_to?(:as)
287
+ field.as(aliaz)
288
+ else
289
+ "#{field} AS #{aliaz}"
290
+ end
253
291
  }
254
292
 
255
- relation = except(:group).group(group.join(','))
293
+ relation = except(:group).group(group)
256
294
  relation.select_values = select_values
257
295
 
258
296
  calculated_data = @klass.connection.select_all(relation)
@@ -264,10 +302,10 @@ module ActiveRecord
264
302
  end
265
303
 
266
304
  ActiveSupport::OrderedHash[calculated_data.map do |row|
267
- key = group_columns.map { |aliaz, column|
305
+ key = group_columns.map { |aliaz, column|
268
306
  type_cast_calculated_value(row[aliaz], column)
269
307
  }
270
- key = key.first if key.size == 1
308
+ key = key.first if key.size == 1
271
309
  key = key_records[key] if associated
272
310
  [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
273
311
  end]
@@ -282,6 +320,7 @@ module ActiveRecord
282
320
  # column_alias_for("count(*)") # => "count_all"
283
321
  # column_alias_for("count", "id") # => "count_id"
284
322
  def column_alias_for(*keys)
323
+ keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k}
285
324
  table_name = keys.join(' ')
286
325
  table_name.downcase!
287
326
  table_name.gsub!(/\*/, 'all')
@@ -293,7 +332,7 @@ module ActiveRecord
293
332
  end
294
333
 
295
334
  def column_for(field)
296
- field_name = field.to_s.split('.').last
335
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
297
336
  @klass.columns.detect { |c| c.name.to_s == field_name }
298
337
  end
299
338
 
@@ -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,8 +134,8 @@ 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?
138
- order("#{primary_key} DESC").limit(*args).reverse
137
+ if order_values.empty? && primary_key
138
+ order("#{quoted_table_name}.#{quoted_primary_key} DESC").limit(*args).reverse
139
139
  else
140
140
  to_a.last(*args)
141
141
  end
@@ -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
@@ -252,9 +253,12 @@ module ActiveRecord
252
253
  orders = relation.order_values.map { |val| val.presence }.compact
253
254
  values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
254
255
 
255
- relation = relation.dup
256
+ relation = relation.dup.select(values)
257
+ relation.uniq_value = nil
258
+
259
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
260
+ ids_array = id_rows.map {|row| row[primary_key]}
256
261
 
257
- ids_array = relation.select(values).collect {|row| row[primary_key]}
258
262
  ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
259
263
  end
260
264
 
@@ -262,9 +266,10 @@ module ActiveRecord
262
266
  conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
263
267
  result = where(conditions).send(match.finder)
264
268
 
265
- if match.bang? && result.blank?
269
+ if match.bang? && result.nil?
266
270
  raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
267
271
  else
272
+ yield(result) if block_given?
268
273
  result
269
274
  end
270
275
  end
@@ -289,7 +294,7 @@ module ActiveRecord
289
294
  r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
290
295
  end
291
296
  yield(record) if block_given?
292
- record.save if match.instantiator == :create
297
+ record.send(match.save_method) if match.save_record?
293
298
  end
294
299
 
295
300
  record
@@ -27,21 +27,23 @@ module ActiveRecord
27
27
  value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
28
28
  attribute.in(value.arel.ast)
29
29
  when Array, ActiveRecord::Associations::CollectionProxy
30
- values = value.to_a.map { |x|
31
- x.is_a?(ActiveRecord::Base) ? x.id : x
32
- }
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)}
33
34
 
34
35
  if values.include?(nil)
35
36
  values = values.compact
36
37
  if values.empty?
37
- attribute.eq nil
38
+ array_predicates << attribute.eq(nil)
38
39
  else
39
- attribute.in(values.compact).or attribute.eq(nil)
40
+ array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
40
41
  end
41
42
  else
42
- attribute.in(values)
43
+ array_predicates << attribute.in(values)
43
44
  end
44
45
 
46
+ array_predicates.inject {|composite, predicate| composite.or(predicate)}
45
47
  when Range, Arel::Relation
46
48
  attribute.in(value)
47
49
  when ActiveRecord::Base
@@ -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
@@ -17,26 +17,27 @@ module ActiveRecord
17
17
  if method == :includes
18
18
  merged_relation = merged_relation.includes(value)
19
19
  else
20
- merged_relation.send(:"#{method}_values=", value)
20
+ merge_relation_method(merged_relation, method, value)
21
21
  end
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
- merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
27
+ merge_relation_method(merged_relation, method, value) if value.present?
28
28
  end
29
29
 
30
- merged_relation.joins_values += r.joins_values
30
+ merge_joins(merged_relation, r)
31
31
 
32
32
  merged_wheres = @where_values + r.where_values
33
33
 
34
34
  unless @where_values.empty?
35
- # Remove duplicates, last one wins.
35
+ # Remove duplicate ARel attributes. Last one wins.
36
36
  seen = Hash.new { |h,table| h[table] = {} }
37
37
  merged_wheres = merged_wheres.reverse.reject { |w|
38
38
  nuke = false
39
- if w.respond_to?(:operator) && w.operator == :==
39
+ if w.respond_to?(:operator) && w.operator == :== &&
40
+ w.left.respond_to?(:relation)
40
41
  name = w.left.name
41
42
  table = w.left.relation.name
42
43
  nuke = seen[table][name]
@@ -48,7 +49,7 @@ module ActiveRecord
48
49
 
49
50
  merged_relation.where_values = merged_wheres
50
51
 
51
- (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with]).each do |method|
52
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
52
53
  value = r.send(:"#{method}_value")
53
54
  merged_relation.send(:"#{method}_value=", value) unless value.nil?
54
55
  end
@@ -57,6 +58,15 @@ module ActiveRecord
57
58
 
58
59
  merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
59
60
 
61
+ if (r.reordering_value)
62
+ # override any order specified in the original relation
63
+ merged_relation.reordering_value = true
64
+ merged_relation.order_values = r.order_values
65
+ else
66
+ # merge in order_values from r
67
+ merged_relation.order_values += r.order_values
68
+ end
69
+
60
70
  # Apply scope extension modules
61
71
  merged_relation.send :apply_modules, r.extensions
62
72
 
@@ -135,5 +145,36 @@ module ActiveRecord
135
145
  relation
136
146
  end
137
147
 
148
+ private
149
+
150
+ def merge_joins(relation, other)
151
+ values = other.joins_values
152
+ return if values.blank?
153
+
154
+ if other.klass >= relation.klass
155
+ relation.joins_values += values
156
+ else
157
+ joins_dependency, rest = values.partition do |join|
158
+ case join
159
+ when Hash, Symbol, Array
160
+ true
161
+ else
162
+ false
163
+ end
164
+ end
165
+
166
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
167
+ other.klass,
168
+ joins_dependency,
169
+ []
170
+ )
171
+
172
+ relation.joins_values += join_dependency.join_associations + rest
173
+ end
174
+ end
175
+
176
+ def merge_relation_method(relation, method, value)
177
+ relation.send(:"#{method}_values=", relation.send(:"#{method}_values") + value)
178
+ end
138
179
  end
139
180
  end