activerecord 3.0.0.beta4 → 3.0.0.rc

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 (69) hide show
  1. data/CHANGELOG +267 -254
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +9 -9
  4. data/lib/active_record/aggregations.rb +3 -4
  5. data/lib/active_record/association_preload.rb +15 -10
  6. data/lib/active_record/associations.rb +54 -37
  7. data/lib/active_record/associations/association_collection.rb +43 -17
  8. data/lib/active_record/associations/association_proxy.rb +2 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +22 -7
  12. data/lib/active_record/associations/has_many_association.rb +6 -1
  13. data/lib/active_record/associations/has_many_through_association.rb +1 -0
  14. data/lib/active_record/associations/has_one_association.rb +1 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +1 -0
  16. data/lib/active_record/associations/through_association_scope.rb +3 -2
  17. data/lib/active_record/attribute_methods.rb +1 -0
  18. data/lib/active_record/autosave_association.rb +4 -6
  19. data/lib/active_record/base.rb +106 -240
  20. data/lib/active_record/callbacks.rb +4 -25
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +22 -29
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -8
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +56 -7
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -18
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +2 -2
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +65 -69
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -6
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +20 -46
  31. data/lib/active_record/counter_cache.rb +14 -4
  32. data/lib/active_record/dynamic_finder_match.rb +9 -0
  33. data/lib/active_record/dynamic_scope_match.rb +7 -0
  34. data/lib/active_record/errors.rb +3 -0
  35. data/lib/active_record/fixtures.rb +5 -6
  36. data/lib/active_record/locale/en.yml +1 -1
  37. data/lib/active_record/locking/optimistic.rb +1 -0
  38. data/lib/active_record/log_subscriber.rb +48 -0
  39. data/lib/active_record/migration.rb +64 -37
  40. data/lib/active_record/named_scope.rb +33 -19
  41. data/lib/active_record/nested_attributes.rb +17 -13
  42. data/lib/active_record/observer.rb +13 -6
  43. data/lib/active_record/persistence.rb +55 -22
  44. data/lib/active_record/query_cache.rb +1 -0
  45. data/lib/active_record/railtie.rb +14 -8
  46. data/lib/active_record/railties/controller_runtime.rb +2 -2
  47. data/lib/active_record/railties/databases.rake +63 -33
  48. data/lib/active_record/reflection.rb +46 -28
  49. data/lib/active_record/relation.rb +38 -24
  50. data/lib/active_record/relation/finder_methods.rb +5 -5
  51. data/lib/active_record/relation/predicate_builder.rb +2 -4
  52. data/lib/active_record/relation/query_methods.rb +134 -115
  53. data/lib/active_record/relation/spawn_methods.rb +1 -1
  54. data/lib/active_record/schema.rb +2 -0
  55. data/lib/active_record/schema_dumper.rb +15 -12
  56. data/lib/active_record/serialization.rb +2 -0
  57. data/lib/active_record/session_store.rb +93 -79
  58. data/lib/active_record/test_case.rb +3 -0
  59. data/lib/active_record/timestamp.rb +49 -29
  60. data/lib/active_record/transactions.rb +5 -2
  61. data/lib/active_record/validations.rb +5 -2
  62. data/lib/active_record/validations/associated.rb +1 -1
  63. data/lib/active_record/validations/uniqueness.rb +1 -1
  64. data/lib/active_record/version.rb +1 -1
  65. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -6
  66. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  67. metadata +27 -14
  68. data/README +0 -351
  69. data/lib/active_record/railties/log_subscriber.rb +0 -32
@@ -1,12 +1,16 @@
1
1
  module ActiveRecord
2
+ # = Active Record Reflection
2
3
  module Reflection # :nodoc:
3
4
  extend ActiveSupport::Concern
4
5
 
5
- # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
6
- # This information can, for example, be used in a form builder that took an Active Record object and created input
7
- # fields for all of the attributes depending on their type and displayed the associations to other objects.
6
+ # Reflection allows you to interrogate Active Record classes and objects
7
+ # about their associations and aggregations. This information can,
8
+ # for example, be used in a form builder that took an Active Record object
9
+ # and created input fields for all of the attributes depending on their type
10
+ # and displayed the associations to other objects.
8
11
  #
9
- # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
12
+ # You can find the interface for the AggregateReflection and AssociationReflection
13
+ # classes in the abstract MacroReflection class.
10
14
  module ClassMethods
11
15
  def create_reflection(macro, name, options, active_record)
12
16
  case macro
@@ -43,8 +47,11 @@ module ActiveRecord
43
47
  reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
44
48
  end
45
49
 
46
- # Returns an array of AssociationReflection objects for all the associations in the class. If you only want to reflect on a
47
- # certain association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>, <tt>:belongs_to</tt>) for that as the first parameter.
50
+ # Returns an array of AssociationReflection objects for all the
51
+ # associations in the class. If you only want to reflect on a certain
52
+ # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
53
+ # <tt>:belongs_to</tt>) as the first parameter.
54
+ #
48
55
  # Example:
49
56
  #
50
57
  # Account.reflect_on_all_associations # returns an array of all associations
@@ -55,9 +62,9 @@ module ActiveRecord
55
62
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
56
63
  end
57
64
 
58
- # Returns the AssociationReflection object for the named +association+ (use the symbol). Example:
65
+ # Returns the AssociationReflection object for the +association+ (use the symbol).
59
66
  #
60
- # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
67
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
61
68
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
62
69
  #
63
70
  def reflect_on_association(association)
@@ -71,8 +78,9 @@ module ActiveRecord
71
78
  end
72
79
 
73
80
 
74
- # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
75
- # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
81
+ # Abstract base class for AggregateReflection and AssociationReflection that
82
+ # describes the interface available for both of those classes. Objects of
83
+ # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
76
84
  class MacroReflection
77
85
  attr_reader :active_record
78
86
 
@@ -80,32 +88,37 @@ module ActiveRecord
80
88
  @macro, @name, @options, @active_record = macro, name, options, active_record
81
89
  end
82
90
 
83
- # Returns the name of the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> will return
84
- # <tt>:balance</tt> or for <tt>has_many :clients</tt> it will return <tt>:clients</tt>.
91
+ # Returns the name of the macro.
92
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:balance</tt>
93
+ # <tt>has_many :clients</tt> will return <tt>:clients</tt>
85
94
  def name
86
95
  @name
87
96
  end
88
97
 
89
- # Returns the macro type. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
90
- # or for <tt>has_many :clients</tt> will return <tt>:has_many</tt>.
98
+ # Returns the macro type.
99
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
100
+ # <tt>has_many :clients</tt> will return <tt>:has_many</tt>
91
101
  def macro
92
102
  @macro
93
103
  end
94
104
 
95
- # Returns the hash of options used for the macro. For example, it would return <tt>{ :class_name => "Money" }</tt> for
96
- # <tt>composed_of :balance, :class_name => 'Money'</tt> or +{}+ for <tt>has_many :clients</tt>.
105
+ # Returns the hash of options used for the macro.
106
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>{ :class_name => "Money" }</tt>
107
+ # <tt>has_many :clients</tt> will return +{}+
97
108
  def options
98
109
  @options
99
110
  end
100
111
 
101
- # Returns the class for the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money
102
- # class and <tt>has_many :clients</tt> returns the Client class.
112
+ # Returns the class for the macro.
113
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> will return the Money class
114
+ # <tt>has_many :clients</tt> will return the Client class
103
115
  def klass
104
116
  @klass ||= class_name.constantize
105
117
  end
106
118
 
107
- # Returns the class name for the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
108
- # and <tt>has_many :clients</tt> returns <tt>'Client'</tt>.
119
+ # Returns the class name for the macro.
120
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>'Money'</tt>
121
+ # <tt>has_many :clients</tt> will return <tt>'Client'</tt>
109
122
  def class_name
110
123
  @class_name ||= options[:class_name] || derive_class_name
111
124
  end
@@ -132,11 +145,13 @@ module ActiveRecord
132
145
  end
133
146
 
134
147
 
135
- # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
148
+ # Holds all the meta-data about an aggregation as it was specified in the
149
+ # Active Record class.
136
150
  class AggregateReflection < MacroReflection #:nodoc:
137
151
  end
138
152
 
139
- # Holds all the meta-data about an association as it was specified in the Active Record class.
153
+ # Holds all the meta-data about an association as it was specified in the
154
+ # Active Record class.
140
155
  class AssociationReflection < MacroReflection #:nodoc:
141
156
  # Returns the target association's class:
142
157
  #
@@ -165,14 +180,14 @@ module ActiveRecord
165
180
  klass.new(*options)
166
181
  end
167
182
 
168
- # Creates a new instance of the associated class, and immediates saves it
183
+ # Creates a new instance of the associated class, and immediately saves it
169
184
  # with ActiveRecord::Base#save. +options+ will be passed to the class's
170
185
  # creation method. Returns the newly created object.
171
186
  def create_association(*options)
172
187
  klass.create(*options)
173
188
  end
174
189
 
175
- # Creates a new instance of the associated class, and immediates saves it
190
+ # Creates a new instance of the associated class, and immediately saves it
176
191
  # with ActiveRecord::Base#save!. +options+ will be passed to the class's
177
192
  # creation method. If the created record doesn't pass validations, then an
178
193
  # exception will be raised.
@@ -267,10 +282,10 @@ module ActiveRecord
267
282
  # Returns whether or not the association should be validated as part of
268
283
  # the parent's validation.
269
284
  #
270
- # Unless you explicitely disable validation with
285
+ # Unless you explicitly disable validation with
271
286
  # <tt>:validate => false</tt>, it will take place when:
272
287
  #
273
- # * you explicitely enable validation; <tt>:validate => true</tt>
288
+ # * you explicitly enable validation; <tt>:validate => true</tt>
274
289
  # * you use autosave; <tt>:autosave => true</tt>
275
290
  # * the association is a +has_many+ association
276
291
  def validate?
@@ -306,9 +321,12 @@ module ActiveRecord
306
321
  end
307
322
  end
308
323
 
309
- # Holds all the meta-data about a :through association as it was specified in the Active Record class.
324
+ # Holds all the meta-data about a :through association as it was specified
325
+ # in the Active Record class.
310
326
  class ThroughReflection < AssociationReflection #:nodoc:
311
- # Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
327
+ # Gets the source of the through reflection. It checks both a singularized
328
+ # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
329
+ #
312
330
  # (The <tt>:tags</tt> association on Tagging below.)
313
331
  #
314
332
  # class Post < ActiveRecord::Base
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/object/blank'
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record Relation
4
5
  class Relation
5
6
  JoinOperation = Struct.new(:relation, :join_class, :on)
6
7
  ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
@@ -9,27 +10,26 @@ module ActiveRecord
9
10
 
10
11
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
11
12
 
12
- delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
13
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
13
14
  delegate :insert, :to => :arel
14
15
 
15
- attr_reader :table, :klass
16
+ attr_reader :table, :klass, :loaded
16
17
  attr_accessor :extensions
18
+ alias :loaded? :loaded
17
19
 
18
- def initialize(klass, table, &block)
20
+ def initialize(klass, table)
19
21
  @klass, @table = klass, table
20
22
 
21
23
  @implicit_readonly = nil
22
- @loaded = nil
24
+ @loaded = false
23
25
 
24
26
  SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
25
27
  (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
26
28
  @extensions = []
27
-
28
- apply_modules(Module.new(&block)) if block_given?
29
29
  end
30
30
 
31
31
  def new(*args, &block)
32
- with_create_scope { @klass.new(*args, &block) }
32
+ scoping { @klass.new(*args, &block) }
33
33
  end
34
34
 
35
35
  def initialize_copy(other)
@@ -39,11 +39,11 @@ module ActiveRecord
39
39
  alias build new
40
40
 
41
41
  def create(*args, &block)
42
- with_create_scope { @klass.create(*args, &block) }
42
+ scoping { @klass.create(*args, &block) }
43
43
  end
44
44
 
45
45
  def create!(*args, &block)
46
- with_create_scope { @klass.create!(*args, &block) }
46
+ scoping { @klass.create!(*args, &block) }
47
47
  end
48
48
 
49
49
  def respond_to?(method, include_private = false)
@@ -67,7 +67,7 @@ module ActiveRecord
67
67
  preload += @includes_values unless eager_loading?
68
68
  preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
69
69
 
70
- # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
70
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there are JOINS and no explicit SELECT.
71
71
  readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
72
72
  @records.each { |record| record.readonly! } if readonly
73
73
 
@@ -75,10 +75,14 @@ module ActiveRecord
75
75
  @records
76
76
  end
77
77
 
78
+ def as_json(options = nil) to_a end #:nodoc:
79
+
80
+ # Returns size of the records.
78
81
  def size
79
82
  loaded? ? @records.length : count
80
83
  end
81
84
 
85
+ # Returns true if there are no records.
82
86
  def empty?
83
87
  loaded? ? @records.empty? : count.zero?
84
88
  end
@@ -99,6 +103,25 @@ module ActiveRecord
99
103
  end
100
104
  end
101
105
 
106
+ # Scope all queries to the current scope.
107
+ #
108
+ # ==== Example
109
+ #
110
+ # Comment.where(:post_id => 1).scoping do
111
+ # Comment.first #=> SELECT * FROM comments WHERE post_id = 1
112
+ # end
113
+ #
114
+ # Please check unscoped if you want to remove all previous scopes (including
115
+ # the default_scope) during the execution of a block.
116
+ def scoping
117
+ @klass.scoped_methods << self
118
+ begin
119
+ yield
120
+ ensure
121
+ @klass.scoped_methods.pop
122
+ end
123
+ end
124
+
102
125
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
103
126
  # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
104
127
  # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
@@ -193,8 +216,7 @@ module ActiveRecord
193
216
  if conditions
194
217
  where(conditions).destroy_all
195
218
  else
196
- to_a.each {|object| object.destroy}
197
- reset
219
+ to_a.each {|object| object.destroy }.tap { reset }
198
220
  end
199
221
  end
200
222
 
@@ -240,8 +262,9 @@ module ActiveRecord
240
262
  # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
241
263
  # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
242
264
  #
243
- # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
244
- # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
265
+ # Both calls delete the affected posts all at once with a single DELETE statement.
266
+ # If you need to destroy dependent associations or call your <tt>before_*</tt> or
267
+ # +after_destroy+ callbacks, use the +destroy_all+ method instead.
245
268
  def delete_all(conditions = nil)
246
269
  conditions ? where(conditions).delete_all : arel.delete.tap { reset }
247
270
  end
@@ -270,10 +293,6 @@ module ActiveRecord
270
293
  where(@klass.primary_key => id_or_array).delete_all
271
294
  end
272
295
 
273
- def loaded?
274
- @loaded
275
- end
276
-
277
296
  def reload
278
297
  reset
279
298
  to_a # force reload
@@ -301,7 +320,6 @@ module ActiveRecord
301
320
  if where.is_a?(Arel::Predicates::Equality)
302
321
  hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
303
322
  end
304
-
305
323
  hash
306
324
  end
307
325
  end
@@ -332,7 +350,7 @@ module ActiveRecord
332
350
  elsif @klass.scopes[method]
333
351
  merge(@klass.send(method, *args, &block))
334
352
  elsif @klass.respond_to?(method)
335
- @klass.send(:with_scope, self) { @klass.send(method, *args, &block) }
353
+ scoping { @klass.send(method, *args, &block) }
336
354
  elsif arel.respond_to?(method)
337
355
  arel.send(method, *args, &block)
338
356
  elsif match = DynamicFinderMatch.match(method)
@@ -351,10 +369,6 @@ module ActiveRecord
351
369
 
352
370
  private
353
371
 
354
- def with_create_scope
355
- @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
356
- end
357
-
358
372
  def references_eager_loaded_tables?
359
373
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
360
374
  joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map(&:downcase).uniq
@@ -87,8 +87,8 @@ module ActiveRecord
87
87
  # person.visits += 1
88
88
  # person.save!
89
89
  # end
90
- def find(*args, &block)
91
- return to_a.find(&block) if block_given?
90
+ def find(*args)
91
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
92
92
 
93
93
  options = args.extract_options!
94
94
 
@@ -259,8 +259,8 @@ module ActiveRecord
259
259
  record
260
260
  end
261
261
 
262
- def find_with_ids(*ids, &block)
263
- return to_a.find(&block) if block_given?
262
+ def find_with_ids(*ids)
263
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
264
264
 
265
265
  expects_array = ids.first.kind_of?(Array)
266
266
  return ids.first if expects_array && ids.first.empty?
@@ -339,7 +339,7 @@ module ActiveRecord
339
339
  end
340
340
 
341
341
  def using_limitable_reflections?(reflections)
342
- reflections.collect(&:collection?).length.zero?
342
+ reflections.none?(&:collection?)
343
343
  end
344
344
 
345
345
  end
@@ -20,15 +20,13 @@ module ActiveRecord
20
20
  table = Arel::Table.new(table_name, :engine => @engine)
21
21
  end
22
22
 
23
- unless attribute = table[column]
24
- raise StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
25
- end
23
+ attribute = table[column] || Arel::Attribute.new(table, column)
26
24
 
27
25
  case value
28
26
  when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
29
27
  values = value.to_a
30
28
  attribute.in(values)
31
- when Range
29
+ when Range, Arel::Relation
32
30
  attribute.in(value)
33
31
  else
34
32
  attribute.eq(value)
@@ -5,79 +5,92 @@ module ActiveRecord
5
5
  module QueryMethods
6
6
  extend ActiveSupport::Concern
7
7
 
8
- included do
9
- (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
10
- attr_accessor :"#{query_method}_values"
11
-
12
- next if [:where, :having, :select].include?(query_method)
13
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
14
- def #{query_method}(*args, &block)
15
- new_relation = clone
16
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
17
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
18
- new_relation.#{query_method}_values += value if value.present?
19
- new_relation
20
- end
21
- CEVAL
22
- end
8
+ attr_accessor :includes_values, :eager_load_values, :preload_values,
9
+ :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values,
10
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
23
11
 
24
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
25
- def select(*args, &block)
26
- if block_given?
27
- to_a.select(&block)
28
- else
29
- new_relation = clone
30
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
31
- new_relation.select_values += value if value.present?
32
- new_relation
33
- end
34
- end
35
- CEVAL
36
-
37
- [:where, :having].each do |query_method|
38
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
39
- def #{query_method}(*args, &block)
40
- new_relation = clone
41
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
42
- value = build_where(*args)
43
- new_relation.#{query_method}_values += Array.wrap(value) if value.present?
44
- new_relation
45
- end
46
- CEVAL
47
- end
12
+ def includes(*args)
13
+ args.reject! { |a| a.blank? }
14
+ clone.tap {|r| r.includes_values += args if args.present? }
15
+ end
16
+
17
+ def eager_load(*args)
18
+ clone.tap {|r| r.eager_load_values += args if args.present? }
19
+ end
48
20
 
49
- ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
50
- attr_accessor :"#{query_method}_value"
21
+ def preload(*args)
22
+ clone.tap {|r| r.preload_values += args if args.present? }
23
+ end
51
24
 
52
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
53
- def #{query_method}(value = true, &block)
54
- new_relation = clone
55
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
56
- new_relation.#{query_method}_value = value
57
- new_relation
58
- end
59
- CEVAL
25
+ def select(*args)
26
+ if block_given?
27
+ to_a.select {|*block_args| yield(*block_args) }
28
+ else
29
+ clone.tap {|r| r.select_values += args if args.present? }
60
30
  end
61
31
  end
62
32
 
63
- def extending(*modules)
64
- new_relation = clone
65
- new_relation.send :apply_modules, *modules
66
- new_relation
33
+ def group(*args)
34
+ clone.tap {|r| r.group_values += args if args.present? }
67
35
  end
68
36
 
69
- def lock(locks = true, &block)
70
- relation = clone
71
- relation.send(:apply_modules, Module.new(&block)) if block_given?
37
+ def order(*args)
38
+ clone.tap {|r| r.order_values += args if args.present? }
39
+ end
40
+
41
+ def reorder(*args)
42
+ clone.tap {|r| r.order_values = args if args.present? }
43
+ end
72
44
 
45
+ def joins(*args)
46
+ args.flatten!
47
+ clone.tap {|r| r.joins_values += args if args.present? }
48
+ end
49
+
50
+ def where(*args)
51
+ value = build_where(*args)
52
+ clone.tap {|r| r.where_values += Array.wrap(value) if value.present? }
53
+ end
54
+
55
+ def having(*args)
56
+ value = build_where(*args)
57
+ clone.tap {|r| r.having_values += Array.wrap(value) if value.present? }
58
+ end
59
+
60
+ def limit(value = true)
61
+ clone.tap {|r| r.limit_value = value }
62
+ end
63
+
64
+ def offset(value = true)
65
+ clone.tap {|r| r.offset_value = value }
66
+ end
67
+
68
+ def lock(locks = true)
73
69
  case locks
74
70
  when String, TrueClass, NilClass
75
- clone.tap {|new_relation| new_relation.lock_value = locks || true }
71
+ clone.tap {|r| r.lock_value = locks || true }
76
72
  else
77
- clone.tap {|new_relation| new_relation.lock_value = false }
73
+ clone.tap {|r| r.lock_value = false }
78
74
  end
79
75
  end
80
76
 
77
+ def readonly(value = true)
78
+ clone.tap {|r| r.readonly_value = value }
79
+ end
80
+
81
+ def create_with(value = true)
82
+ clone.tap {|r| r.create_with_value = value }
83
+ end
84
+
85
+ def from(value = true)
86
+ clone.tap {|r| r.from_value = value }
87
+ end
88
+
89
+ def extending(*modules, &block)
90
+ modules << Module.new(&block) if block_given?
91
+ clone.tap {|r| r.send(:apply_modules, *modules) }
92
+ end
93
+
81
94
  def reverse_order
82
95
  order_clause = arel.send(:order_clauses).join(', ')
83
96
  relation = except(:order)
@@ -116,45 +129,7 @@ module ActiveRecord
116
129
  def build_arel
117
130
  arel = table
118
131
 
119
- joined_associations = []
120
- association_joins = []
121
-
122
- joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
123
-
124
- joins.each do |join|
125
- association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
126
- end
127
-
128
- stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
129
-
130
- non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
131
- custom_joins = custom_join_sql(*non_association_joins)
132
-
133
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
134
-
135
- join_dependency.graft(*stashed_association_joins)
136
-
137
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
138
-
139
- to_join = []
140
-
141
- join_dependency.join_associations.each do |association|
142
- if (association_relation = association.relation).is_a?(Array)
143
- to_join << [association_relation.first, association.join_class, association.association_join.first]
144
- to_join << [association_relation.last, association.join_class, association.association_join.last]
145
- else
146
- to_join << [association_relation, association.join_class, association.association_join]
147
- end
148
- end
149
-
150
- to_join.each do |tj|
151
- unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
152
- joined_associations << tj
153
- arel = arel.join(tj[0], tj[1]).on(*tj[2])
154
- end
155
- end
156
-
157
- arel = arel.join(custom_joins)
132
+ arel = build_joins(arel, @joins_values) if @joins_values.present?
158
133
 
159
134
  @where_values.uniq.each do |where|
160
135
  next if where.blank?
@@ -168,31 +143,18 @@ module ActiveRecord
168
143
  end
169
144
  end
170
145
 
171
- @having_values.uniq.each do |h|
172
- arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
173
- end
146
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
174
147
 
175
148
  arel = arel.take(@limit_value) if @limit_value.present?
176
149
  arel = arel.skip(@offset_value) if @offset_value.present?
177
150
 
178
- arel = arel.group(*@group_values.uniq.select{|g| g.present?})
179
-
180
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}.map(&:to_s))
151
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
181
152
 
182
- selects = @select_values.uniq
153
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
183
154
 
184
- quoted_table_name = @klass.quoted_table_name
185
-
186
- if selects.present?
187
- selects.each do |s|
188
- @implicit_readonly = false
189
- arel = arel.project(s) if s.present?
190
- end
191
- else
192
- arel = arel.project(quoted_table_name + '.*')
193
- end
155
+ arel = build_select(arel, @select_values.uniq)
194
156
 
195
- arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name)
157
+ arel = arel.from(@from_value) if @from_value.present?
196
158
 
197
159
  case @lock_value
198
160
  when TrueClass
@@ -221,6 +183,63 @@ module ActiveRecord
221
183
 
222
184
  private
223
185
 
186
+ def build_joins(relation, joins)
187
+ joined_associations = []
188
+ association_joins = []
189
+
190
+ joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
191
+
192
+ joins.each do |join|
193
+ association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
194
+ end
195
+
196
+ stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
197
+
198
+ non_association_joins = (joins - association_joins - stashed_association_joins)
199
+ custom_joins = custom_join_sql(*non_association_joins)
200
+
201
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
202
+
203
+ join_dependency.graft(*stashed_association_joins)
204
+
205
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
206
+
207
+ to_join = []
208
+
209
+ join_dependency.join_associations.each do |association|
210
+ if (association_relation = association.relation).is_a?(Array)
211
+ to_join << [association_relation.first, association.join_class, association.association_join.first]
212
+ to_join << [association_relation.last, association.join_class, association.association_join.last]
213
+ else
214
+ to_join << [association_relation, association.join_class, association.association_join]
215
+ end
216
+ end
217
+
218
+ to_join.each do |tj|
219
+ unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
220
+ joined_associations << tj
221
+ relation = relation.join(tj[0], tj[1]).on(*tj[2])
222
+ end
223
+ end
224
+
225
+ relation.join(custom_joins)
226
+ end
227
+
228
+ def build_select(arel, selects)
229
+ if selects.present?
230
+ @implicit_readonly = false
231
+ # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
232
+ # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
233
+ if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
234
+ arel.project(*selects)
235
+ else
236
+ arel.project(selects.last)
237
+ end
238
+ else
239
+ arel.project(@klass.quoted_table_name + '.*')
240
+ end
241
+ end
242
+
224
243
  def apply_modules(modules)
225
244
  values = Array.wrap(modules)
226
245
  @extensions += values if values.present?