activerecord 3.0.1 → 3.0.2

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 (46) hide show
  1. data/CHANGELOG +33 -0
  2. data/examples/performance.rb +18 -1
  3. data/lib/active_record.rb +3 -3
  4. data/lib/active_record/aggregations.rb +2 -2
  5. data/lib/active_record/association_preload.rb +1 -1
  6. data/lib/active_record/associations.rb +59 -26
  7. data/lib/active_record/associations/association_collection.rb +28 -18
  8. data/lib/active_record/associations/association_proxy.rb +4 -4
  9. data/lib/active_record/associations/belongs_to_association.rb +3 -3
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +10 -13
  11. data/lib/active_record/associations/has_many_through_association.rb +2 -3
  12. data/lib/active_record/associations/has_one_association.rb +6 -6
  13. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  14. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -4
  15. data/lib/active_record/attribute_methods/primary_key.rb +4 -3
  16. data/lib/active_record/autosave_association.rb +7 -7
  17. data/lib/active_record/base.rb +62 -46
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -8
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -2
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -9
  21. data/lib/active_record/connection_adapters/mysql_adapter.rb +7 -7
  22. data/lib/active_record/connection_adapters/sqlite_adapter.rb +2 -2
  23. data/lib/active_record/dynamic_finder_match.rb +20 -17
  24. data/lib/active_record/dynamic_scope_match.rb +6 -15
  25. data/lib/active_record/fixtures.rb +3 -5
  26. data/lib/active_record/locking/optimistic.rb +1 -1
  27. data/lib/active_record/locking/pessimistic.rb +4 -4
  28. data/lib/active_record/nested_attributes.rb +9 -4
  29. data/lib/active_record/persistence.rb +7 -8
  30. data/lib/active_record/railties/databases.rake +7 -7
  31. data/lib/active_record/relation.rb +16 -18
  32. data/lib/active_record/relation/batches.rb +1 -1
  33. data/lib/active_record/relation/calculations.rb +37 -28
  34. data/lib/active_record/relation/finder_methods.rb +19 -19
  35. data/lib/active_record/relation/predicate_builder.rb +3 -1
  36. data/lib/active_record/relation/query_methods.rb +100 -75
  37. data/lib/active_record/relation/spawn_methods.rb +50 -39
  38. data/lib/active_record/serialization.rb +1 -1
  39. data/lib/active_record/session_store.rb +4 -4
  40. data/lib/active_record/transactions.rb +6 -6
  41. data/lib/active_record/validations.rb +1 -1
  42. data/lib/active_record/validations/uniqueness.rb +6 -1
  43. data/lib/active_record/version.rb +1 -1
  44. data/lib/rails/generators/active_record.rb +2 -10
  45. data/lib/rails/generators/active_record/migration.rb +15 -0
  46. metadata +25 -11
@@ -50,7 +50,7 @@ module ActiveRecord
50
50
  def find_in_batches(options = {})
51
51
  relation = self
52
52
 
53
- if orders.present? || taken.present?
53
+ unless arel.orders.blank? && arel.taken.blank?
54
54
  ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
55
55
  end
56
56
 
@@ -14,9 +14,9 @@ module ActiveRecord
14
14
  #
15
15
  # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
16
16
  # See conditions in the intro to ActiveRecord::Base.
17
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
17
+ # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
18
18
  # (rarely needed) or named associations in the same form used for the <tt>:include</tt> option, which will
19
- # perform an INNER JOIN on the associated table(s). If the value is a string, then the records
19
+ # perform an INNER JOIN on the associated table(s). If the value is a string, then the records
20
20
  # will be returned read-only since they will have attributes that do not correspond to the table's columns.
21
21
  # Pass <tt>:readonly => false</tt> to override.
22
22
  # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
@@ -175,28 +175,39 @@ module ActiveRecord
175
175
  end
176
176
 
177
177
  distinct = options[:distinct] || distinct
178
- column_name = :all if column_name.blank? && operation == "count"
179
178
 
180
179
  if @group_values.any?
181
- return execute_grouped_calculation(operation, column_name)
180
+ execute_grouped_calculation(operation, column_name, distinct)
182
181
  else
183
- return execute_simple_calculation(operation, column_name, distinct)
182
+ execute_simple_calculation(operation, column_name, distinct)
184
183
  end
185
184
  end
186
185
 
187
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
188
- column = if @klass.column_names.include?(column_name.to_s)
189
- Arel::Attribute.new(@klass.unscoped, column_name)
186
+ def aggregate_column(column_name)
187
+ if @klass.column_names.include?(column_name.to_s)
188
+ Arel::Attribute.new(@klass.unscoped.table, column_name)
190
189
  else
191
- Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
190
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
192
191
  end
192
+ end
193
+
194
+ def operation_over_aggregate_column(column, operation, distinct)
195
+ operation == 'count' ? column.count(distinct) : column.send(operation)
196
+ end
197
+
198
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
199
+ column = aggregate_column(column_name)
193
200
 
194
201
  # Postgresql doesn't like ORDER BY when there are no GROUP BY
195
- relation = except(:order).select(operation == 'count' ? column.count(distinct) : column.send(operation))
202
+ relation = except(:order)
203
+ select_value = operation_over_aggregate_column(column, operation, distinct)
204
+
205
+ relation.select_values = [select_value]
206
+
196
207
  type_cast_calculated_value(@klass.connection.select_value(relation.to_sql), column_for(column_name), operation)
197
208
  end
198
209
 
199
- def execute_grouped_calculation(operation, column_name) #:nodoc:
210
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
200
211
  group_attr = @group_values.first
201
212
  association = @klass.reflect_on_association(group_attr.to_sym)
202
213
  associated = association && association.macro == :belongs_to # only count belongs_to associations
@@ -206,33 +217,31 @@ module ActiveRecord
206
217
 
207
218
  group = @klass.connection.adapter_name == 'FrontBase' ? group_alias : group_field
208
219
 
209
- aggregate_alias = column_alias_for(operation, column_name)
210
-
211
- select_statement = if operation == 'count' && column_name == :all
212
- "COUNT(*) AS count_all"
220
+ if operation == 'count' && column_name == :all
221
+ aggregate_alias = 'count_all'
213
222
  else
214
- Arel::Attribute.new(@klass.unscoped, column_name).send(operation).as(aggregate_alias).to_sql
223
+ aggregate_alias = column_alias_for(operation, column_name)
215
224
  end
216
225
 
217
- select_statement << ", #{group_field} AS #{group_alias}"
218
-
219
- relation = except(:group).select(select_statement).group(group)
226
+ relation = except(:group).group(group)
227
+ relation.select_values = [
228
+ operation_over_aggregate_column(aggregate_column(column_name), operation, distinct).as(aggregate_alias),
229
+ "#{group_field} AS #{group_alias}"
230
+ ]
220
231
 
221
232
  calculated_data = @klass.connection.select_all(relation.to_sql)
222
233
 
223
234
  if association
224
235
  key_ids = calculated_data.collect { |row| row[group_alias] }
225
236
  key_records = association.klass.base_class.find(key_ids)
226
- key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
237
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
227
238
  end
228
239
 
229
- calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
230
- key = type_cast_calculated_value(row[group_alias], group_column)
231
- key = key_records[key] if associated
232
- value = row[aggregate_alias]
233
- all[key] = type_cast_calculated_value(value, column_for(column_name), operation)
234
- all
235
- end
240
+ ActiveSupport::OrderedHash[calculated_data.map do |row|
241
+ key = type_cast_calculated_value(row[group_alias], group_column)
242
+ key = key_records[key] if associated
243
+ [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
244
+ end]
236
245
  end
237
246
 
238
247
  # Converts the given keys to the value that the database adapter returns as
@@ -268,7 +277,7 @@ module ActiveRecord
268
277
  else type_cast_using_column(value, column)
269
278
  end
270
279
  else
271
- value
280
+ type_cast_using_column(value, column)
272
281
  end
273
282
  end
274
283
 
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  #
22
22
  # ==== Parameters
23
23
  #
24
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>,
24
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>["user_name = ?", username]</tt>,
25
25
  # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
26
26
  # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
27
27
  # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
55
55
  # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
56
56
  # Person.find([1]) # returns an array for the object with ID = 1
57
- # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
57
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
58
58
  #
59
59
  # Note that returned records may not be in the same order as the ids you
60
60
  # provide since database rows are unordered. Give an explicit <tt>:order</tt>
@@ -63,23 +63,23 @@ module ActiveRecord
63
63
  # ==== Examples
64
64
  #
65
65
  # # find first
66
- # Person.find(:first) # returns the first object fetched by SELECT * FROM people
67
- # Person.find(:first, :conditions => [ "user_name = ?", user_name])
68
- # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
69
- # Person.find(:first, :order => "created_on DESC", :offset => 5)
66
+ # Person.first # returns the first object fetched by SELECT * FROM people
67
+ # Person.where(["user_name = ?", user_name]).first
68
+ # Person.where(["user_name = :u", { :u => user_name }]).first
69
+ # Person.order("created_on DESC").offset(5).first
70
70
  #
71
71
  # # find last
72
- # Person.find(:last) # returns the last object fetched by SELECT * FROM people
73
- # Person.find(:last, :conditions => [ "user_name = ?", user_name])
74
- # Person.find(:last, :order => "created_on DESC", :offset => 5)
72
+ # Person.last # returns the last object fetched by SELECT * FROM people
73
+ # Person.where(["user_name = ?", user_name]).last
74
+ # Person.order("created_on DESC").offset(5).last
75
75
  #
76
76
  # # find all
77
- # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
78
- # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
79
- # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
80
- # Person.find(:all, :offset => 10, :limit => 10)
81
- # Person.find(:all, :include => [ :account, :friends ])
82
- # Person.find(:all, :group => "category")
77
+ # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
78
+ # Person.where(["category IN (?)", categories]).limit(50).all
79
+ # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
80
+ # Person.offset(10).limit(10).all
81
+ # Person.includes([:account, :friends]).all
82
+ # Person.group("category").all
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
@@ -88,7 +88,7 @@ module ActiveRecord
88
88
  # expected <tt>person.visits == 4</tt>.
89
89
  #
90
90
  # Person.transaction do
91
- # person = Person.find(1, :lock => true)
91
+ # person = Person.lock(true).find(1)
92
92
  # person.visits += 1
93
93
  # person.save!
94
94
  # end
@@ -230,7 +230,7 @@ module ActiveRecord
230
230
  end
231
231
 
232
232
  def find_by_attributes(match, attributes, *args)
233
- conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h}
233
+ conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
234
234
  result = where(conditions).send(match.finder)
235
235
 
236
236
  if match.bang? && result.blank?
@@ -291,8 +291,8 @@ module ActiveRecord
291
291
  record = where(primary_key.eq(id)).first
292
292
 
293
293
  unless record
294
- conditions = arel.wheres.map { |x| x.value }.join(', ')
295
- conditions = " [WHERE #{conditions}]" if conditions.present?
294
+ conditions = arel.where_sql
295
+ conditions = " [#{conditions}]" if conditions
296
296
  raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}"
297
297
  end
298
298
 
@@ -24,7 +24,9 @@ module ActiveRecord
24
24
 
25
25
  case value
26
26
  when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
27
- values = value.to_a
27
+ values = value.to_a.map { |x|
28
+ x.respond_to?(:quoted_id) ? x.quoted_id : x
29
+ }
28
30
  attribute.in(values)
29
31
  when Range, Arel::Relation
30
32
  attribute.in(value)
@@ -6,93 +6,133 @@ module ActiveRecord
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  attr_accessor :includes_values, :eager_load_values, :preload_values,
9
- :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values,
9
+ :select_values, :group_values, :order_values, :reorder_flag, :joins_values, :where_values, :having_values,
10
10
  :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
11
11
 
12
12
  def includes(*args)
13
- args.reject! { |a| a.blank? }
14
- clone.tap {|r| r.includes_values = (r.includes_values + args).flatten.uniq if args.present? }
13
+ args.reject! {|a| a.blank? }
14
+
15
+ return clone if args.empty?
16
+
17
+ relation = clone
18
+ relation.includes_values = (relation.includes_values + args).flatten.uniq
19
+ relation
15
20
  end
16
21
 
17
22
  def eager_load(*args)
18
- clone.tap {|r| r.eager_load_values += args if args.present? }
23
+ relation = clone
24
+ relation.eager_load_values += args unless args.blank?
25
+ relation
19
26
  end
20
27
 
21
28
  def preload(*args)
22
- clone.tap {|r| r.preload_values += args if args.present? }
29
+ relation = clone
30
+ relation.preload_values += args unless args.blank?
31
+ relation
23
32
  end
24
33
 
25
- def select(*args)
34
+ def select(value = Proc.new)
26
35
  if block_given?
27
- to_a.select {|*block_args| yield(*block_args) }
36
+ to_a.select {|*block_args| value.call(*block_args) }
28
37
  else
29
- clone.tap {|r| r.select_values += args if args.present? }
38
+ relation = clone
39
+ relation.select_values += Array.wrap(value)
40
+ relation
30
41
  end
31
42
  end
32
43
 
33
44
  def group(*args)
34
- clone.tap {|r| r.group_values += args.flatten if args.present? }
45
+ relation = clone
46
+ relation.group_values += args.flatten unless args.blank?
47
+ relation
35
48
  end
36
49
 
37
50
  def order(*args)
38
- clone.tap {|r| r.order_values += args if args.present? }
51
+ relation = clone
52
+ relation.order_values += args.flatten unless args.blank?
53
+ relation
39
54
  end
40
55
 
41
56
  def reorder(*args)
42
- clone.tap {|r| r.order_values = args if args.present? }
57
+ ActiveSupport::Deprecation.warn "reorder is deprecated. Please use except(:order).order(...) instead", caller
58
+ relation = clone
59
+ unless args.blank?
60
+ relation.order_values = args
61
+ relation.reorder_flag = true
62
+ end
63
+ relation
43
64
  end
44
65
 
45
66
  def joins(*args)
67
+ relation = clone
68
+
46
69
  args.flatten!
47
- clone.tap {|r| r.joins_values += args if args.present? }
70
+ relation.joins_values += args unless args.blank?
71
+
72
+ relation
48
73
  end
49
74
 
50
75
  def where(opts, *rest)
51
- value = build_where(opts, rest)
52
- copy = clone
53
- copy.where_values += Array.wrap(value) if value
54
- copy
76
+ relation = clone
77
+ relation.where_values += build_where(opts, rest) unless opts.blank?
78
+ relation
55
79
  end
56
80
 
57
81
  def having(*args)
58
- value = build_where(*args)
59
- clone.tap {|r| r.having_values += Array.wrap(value) if value.present? }
82
+ relation = clone
83
+ relation.having_values += build_where(*args) unless args.blank?
84
+ relation
60
85
  end
61
86
 
62
- def limit(value = true)
63
- copy = clone
64
- copy.limit_value = value
65
- copy
87
+ def limit(value)
88
+ relation = clone
89
+ relation.limit_value = value
90
+ relation
66
91
  end
67
92
 
68
- def offset(value = true)
69
- clone.tap {|r| r.offset_value = value }
93
+ def offset(value)
94
+ relation = clone
95
+ relation.offset_value = value
96
+ relation
70
97
  end
71
98
 
72
99
  def lock(locks = true)
100
+ relation = clone
101
+
73
102
  case locks
74
103
  when String, TrueClass, NilClass
75
- clone.tap {|r| r.lock_value = locks || true }
104
+ relation.lock_value = locks || true
76
105
  else
77
- clone.tap {|r| r.lock_value = false }
106
+ relation.lock_value = false
78
107
  end
108
+
109
+ relation
79
110
  end
80
111
 
81
112
  def readonly(value = true)
82
- clone.tap {|r| r.readonly_value = value }
113
+ relation = clone
114
+ relation.readonly_value = value
115
+ relation
83
116
  end
84
117
 
85
- def create_with(value = true)
86
- clone.tap {|r| r.create_with_value = value }
118
+ def create_with(value)
119
+ relation = clone
120
+ relation.create_with_value = value
121
+ relation
87
122
  end
88
123
 
89
- def from(value = true)
90
- clone.tap {|r| r.from_value = value }
124
+ def from(value)
125
+ relation = clone
126
+ relation.from_value = value
127
+ relation
91
128
  end
92
129
 
93
130
  def extending(*modules, &block)
94
131
  modules << Module.new(&block) if block_given?
95
- clone.tap {|r| r.send(:apply_modules, *modules) }
132
+
133
+ relation = clone
134
+ relation.send(:apply_modules, modules.flatten)
135
+ relation
96
136
  end
97
137
 
98
138
  def reverse_order
@@ -103,7 +143,7 @@ module ActiveRecord
103
143
  "#{@klass.table_name}.#{@klass.primary_key} DESC" :
104
144
  reverse_sql_order(order_clause)
105
145
 
106
- relation.order Arel::SqlLiteral.new order
146
+ relation.order(Arel::SqlLiteral.new(order))
107
147
  end
108
148
 
109
149
  def arel
@@ -111,24 +151,23 @@ module ActiveRecord
111
151
  end
112
152
 
113
153
  def custom_join_sql(*joins)
114
- arel = table
154
+ arel = table.select_manager
155
+
115
156
  joins.each do |join|
116
157
  next if join.blank?
117
158
 
118
159
  @implicit_readonly = true
119
160
 
120
161
  case join
121
- when Hash, Array, Symbol
122
- if array_of_strings?(join)
123
- join_string = join.join(' ')
124
- arel = arel.join(Arel::SqlLiteral.new(join_string))
125
- end
162
+ when Array
163
+ join = Arel.sql(join.join(' ')) if array_of_strings?(join)
126
164
  when String
127
- arel = arel.join(Arel::SqlLiteral.new(join))
128
- else
129
- arel = arel.join(join)
165
+ join = Arel.sql(join)
130
166
  end
167
+
168
+ arel.join(join)
131
169
  end
170
+
132
171
  arel.joins(arel)
133
172
  end
134
173
 
@@ -138,23 +177,18 @@ module ActiveRecord
138
177
  arel = build_joins(arel, @joins_values) unless @joins_values.empty?
139
178
 
140
179
  (@where_values - ['']).uniq.each do |where|
141
- case where
142
- when Arel::SqlLiteral
143
- arel = arel.where(where)
144
- else
145
- sql = where.is_a?(String) ? where : where.to_sql
146
- arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
147
- end
180
+ where = Arel.sql(where) if String === where
181
+ arel = arel.where(Arel::Nodes::Grouping.new(where))
148
182
  end
149
183
 
150
- arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty?
184
+ arel = arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
151
185
 
152
186
  arel = arel.take(@limit_value) if @limit_value
153
187
  arel = arel.skip(@offset_value) if @offset_value
154
188
 
155
- arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty?
189
+ arel = arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
156
190
 
157
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty?
191
+ arel = arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty?
158
192
 
159
193
  arel = build_select(arel, @select_values.uniq)
160
194
 
@@ -164,22 +198,21 @@ module ActiveRecord
164
198
  arel
165
199
  end
166
200
 
201
+ private
202
+
167
203
  def build_where(opts, other = [])
168
204
  case opts
169
205
  when String, Array
170
- @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))
206
+ [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
171
207
  when Hash
172
208
  attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
173
209
  PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
174
210
  else
175
- opts
211
+ [opts]
176
212
  end
177
213
  end
178
214
 
179
- private
180
-
181
215
  def build_joins(relation, joins)
182
- joined_associations = []
183
216
  association_joins = []
184
217
 
185
218
  joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
@@ -203,18 +236,15 @@ module ActiveRecord
203
236
 
204
237
  join_dependency.join_associations.each do |association|
205
238
  if (association_relation = association.relation).is_a?(Array)
206
- to_join << [association_relation.first, association.join_class, association.association_join.first]
207
- to_join << [association_relation.last, association.join_class, association.association_join.last]
239
+ to_join << [association_relation.first, association.join_type, association.association_join.first]
240
+ to_join << [association_relation.last, association.join_type, association.association_join.last]
208
241
  else
209
- to_join << [association_relation, association.join_class, association.association_join]
242
+ to_join << [association_relation, association.join_type, association.association_join]
210
243
  end
211
244
  end
212
245
 
213
- to_join.each do |tj|
214
- unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
215
- joined_associations << tj
216
- relation = relation.join(tj[0], tj[1]).on(*tj[2])
217
- end
246
+ to_join.uniq.each do |left, join_type, right|
247
+ relation = relation.join(left, join_type).on(*right)
218
248
  end
219
249
 
220
250
  relation.join(custom_joins)
@@ -223,22 +253,17 @@ module ActiveRecord
223
253
  def build_select(arel, selects)
224
254
  unless selects.empty?
225
255
  @implicit_readonly = false
226
- # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
227
- # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
228
- if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
229
- arel.project(*selects)
230
- else
231
- arel.project(selects.last)
232
- end
256
+ arel.project(*selects)
233
257
  else
234
258
  arel.project(Arel::SqlLiteral.new(@klass.quoted_table_name + '.*'))
235
259
  end
236
260
  end
237
261
 
238
262
  def apply_modules(modules)
239
- values = Array.wrap(modules)
240
- @extensions += values if values.present?
241
- values.each {|extension| extend(extension) }
263
+ unless modules.empty?
264
+ @extensions += modules
265
+ modules.each {|extension| extend(extension) }
266
+ end
242
267
  end
243
268
 
244
269
  def reverse_sql_order(order_query)