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.
- data/CHANGELOG +267 -254
- data/README.rdoc +222 -0
- data/examples/performance.rb +9 -9
- data/lib/active_record/aggregations.rb +3 -4
- data/lib/active_record/association_preload.rb +15 -10
- data/lib/active_record/associations.rb +54 -37
- data/lib/active_record/associations/association_collection.rb +43 -17
- data/lib/active_record/associations/association_proxy.rb +2 -0
- data/lib/active_record/associations/belongs_to_association.rb +1 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +22 -7
- data/lib/active_record/associations/has_many_association.rb +6 -1
- data/lib/active_record/associations/has_many_through_association.rb +1 -0
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/has_one_through_association.rb +1 -0
- data/lib/active_record/associations/through_association_scope.rb +3 -2
- data/lib/active_record/attribute_methods.rb +1 -0
- data/lib/active_record/autosave_association.rb +4 -6
- data/lib/active_record/base.rb +106 -240
- data/lib/active_record/callbacks.rb +4 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +22 -29
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +56 -7
- data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -18
- data/lib/active_record/connection_adapters/mysql_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +65 -69
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +20 -46
- data/lib/active_record/counter_cache.rb +14 -4
- data/lib/active_record/dynamic_finder_match.rb +9 -0
- data/lib/active_record/dynamic_scope_match.rb +7 -0
- data/lib/active_record/errors.rb +3 -0
- data/lib/active_record/fixtures.rb +5 -6
- data/lib/active_record/locale/en.yml +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +64 -37
- data/lib/active_record/named_scope.rb +33 -19
- data/lib/active_record/nested_attributes.rb +17 -13
- data/lib/active_record/observer.rb +13 -6
- data/lib/active_record/persistence.rb +55 -22
- data/lib/active_record/query_cache.rb +1 -0
- data/lib/active_record/railtie.rb +14 -8
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +63 -33
- data/lib/active_record/reflection.rb +46 -28
- data/lib/active_record/relation.rb +38 -24
- data/lib/active_record/relation/finder_methods.rb +5 -5
- data/lib/active_record/relation/predicate_builder.rb +2 -4
- data/lib/active_record/relation/query_methods.rb +134 -115
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/schema.rb +2 -0
- data/lib/active_record/schema_dumper.rb +15 -12
- data/lib/active_record/serialization.rb +2 -0
- data/lib/active_record/session_store.rb +93 -79
- data/lib/active_record/test_case.rb +3 -0
- data/lib/active_record/timestamp.rb +49 -29
- data/lib/active_record/transactions.rb +5 -2
- data/lib/active_record/validations.rb +5 -2
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -6
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- metadata +27 -14
- data/README +0 -351
- 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
|
6
|
-
#
|
7
|
-
#
|
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
|
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
|
47
|
-
#
|
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
|
65
|
+
# Returns the AssociationReflection object for the +association+ (use the symbol).
|
59
66
|
#
|
60
|
-
# Account.reflect_on_association(:owner)
|
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
|
75
|
-
#
|
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.
|
84
|
-
# <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.
|
90
|
-
#
|
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.
|
96
|
-
# <tt>composed_of :balance, :class_name => 'Money'</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.
|
102
|
-
#
|
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.
|
108
|
-
#
|
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
|
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
|
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
|
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
|
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
|
285
|
+
# Unless you explicitly disable validation with
|
271
286
|
# <tt>:validate => false</tt>, it will take place when:
|
272
287
|
#
|
273
|
-
# * you
|
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
|
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
|
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
|
20
|
+
def initialize(klass, table)
|
19
21
|
@klass, @table = klass, table
|
20
22
|
|
21
23
|
@implicit_readonly = nil
|
22
|
-
@loaded =
|
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
|
-
|
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
|
-
|
42
|
+
scoping { @klass.create(*args, &block) }
|
43
43
|
end
|
44
44
|
|
45
45
|
def create!(*args, &block)
|
46
|
-
|
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
|
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.
|
244
|
-
# associations or call your <tt>before_*</tt> or
|
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
|
-
|
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
|
91
|
-
return to_a.find(
|
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
|
263
|
-
return to_a.find(
|
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.
|
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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
50
|
-
|
21
|
+
def preload(*args)
|
22
|
+
clone.tap {|r| r.preload_values += args if args.present? }
|
23
|
+
end
|
51
24
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
64
|
-
|
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
|
70
|
-
|
71
|
-
|
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 {|
|
71
|
+
clone.tap {|r| r.lock_value = locks || true }
|
76
72
|
else
|
77
|
-
clone.tap {|
|
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
|
-
|
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
|
-
|
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
|
-
|
153
|
+
arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
|
183
154
|
|
184
|
-
|
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 =
|
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?
|