activerecord 4.0.4 → 4.1.16
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -96
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'active_support/core_ext/array/wrap'
|
2
|
+
require 'active_model/forbidden_attributes_protection'
|
2
3
|
|
3
4
|
module ActiveRecord
|
4
5
|
module QueryMethods
|
5
6
|
extend ActiveSupport::Concern
|
6
7
|
|
8
|
+
include ActiveModel::ForbiddenAttributesProtection
|
9
|
+
|
7
10
|
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
|
8
11
|
# In this case, #where must be chained with #not to return a new relation.
|
9
12
|
class WhereChain
|
@@ -37,6 +40,8 @@ module ActiveRecord
|
|
37
40
|
def not(opts, *rest)
|
38
41
|
where_value = @scope.send(:build_where, opts, rest).map do |rel|
|
39
42
|
case rel
|
43
|
+
when NilClass
|
44
|
+
raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
|
40
45
|
when Arel::Nodes::In
|
41
46
|
Arel::Nodes::NotIn.new(rel.left, rel.right)
|
42
47
|
when Arel::Nodes::Equality
|
@@ -47,6 +52,8 @@ module ActiveRecord
|
|
47
52
|
Arel::Nodes::Not.new(rel)
|
48
53
|
end
|
49
54
|
end
|
55
|
+
|
56
|
+
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
|
50
57
|
@scope.where_values += where_value
|
51
58
|
@scope
|
52
59
|
end
|
@@ -100,6 +107,14 @@ module ActiveRecord
|
|
100
107
|
# firing an additional query. This will often result in a
|
101
108
|
# performance improvement over a simple +join+.
|
102
109
|
#
|
110
|
+
# You can also specify multiple relationships, like this:
|
111
|
+
#
|
112
|
+
# users = User.includes(:address, :friends)
|
113
|
+
#
|
114
|
+
# Loading nested relationships is possible using a Hash:
|
115
|
+
#
|
116
|
+
# users = User.includes(:address, friends: [:address, :followers])
|
117
|
+
#
|
103
118
|
# === conditions
|
104
119
|
#
|
105
120
|
# If you want to add conditions to your included models you'll have
|
@@ -110,15 +125,19 @@ module ActiveRecord
|
|
110
125
|
# Will throw an error, but this will work:
|
111
126
|
#
|
112
127
|
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
128
|
+
#
|
129
|
+
# Note that +includes+ works with association names while +references+ needs
|
130
|
+
# the actual table name.
|
113
131
|
def includes(*args)
|
114
|
-
check_if_method_has_arguments!(
|
132
|
+
check_if_method_has_arguments!(:includes, args)
|
115
133
|
spawn.includes!(*args)
|
116
134
|
end
|
117
135
|
|
118
136
|
def includes!(*args) # :nodoc:
|
119
|
-
args.reject!
|
137
|
+
args.reject!(&:blank?)
|
138
|
+
args.flatten!
|
120
139
|
|
121
|
-
self.includes_values
|
140
|
+
self.includes_values |= args
|
122
141
|
self
|
123
142
|
end
|
124
143
|
|
@@ -129,7 +148,7 @@ module ActiveRecord
|
|
129
148
|
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
130
149
|
# "users"."id"
|
131
150
|
def eager_load(*args)
|
132
|
-
check_if_method_has_arguments!(
|
151
|
+
check_if_method_has_arguments!(:eager_load, args)
|
133
152
|
spawn.eager_load!(*args)
|
134
153
|
end
|
135
154
|
|
@@ -143,7 +162,7 @@ module ActiveRecord
|
|
143
162
|
# User.preload(:posts)
|
144
163
|
# => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
|
145
164
|
def preload(*args)
|
146
|
-
check_if_method_has_arguments!(
|
165
|
+
check_if_method_has_arguments!(:preload, args)
|
147
166
|
spawn.preload!(*args)
|
148
167
|
end
|
149
168
|
|
@@ -152,23 +171,26 @@ module ActiveRecord
|
|
152
171
|
self
|
153
172
|
end
|
154
173
|
|
155
|
-
#
|
156
|
-
# therefore be JOINed in any query rather than loaded separately.
|
174
|
+
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
175
|
+
# and should therefore be JOINed in any query rather than loaded separately.
|
176
|
+
# This method only works in conjuction with +includes+.
|
177
|
+
# See #includes for more details.
|
157
178
|
#
|
158
179
|
# User.includes(:posts).where("posts.name = 'foo'")
|
159
180
|
# # => Doesn't JOIN the posts table, resulting in an error.
|
160
181
|
#
|
161
182
|
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
162
183
|
# # => Query now knows the string references posts, so adds a JOIN
|
163
|
-
def references(*
|
164
|
-
check_if_method_has_arguments!(
|
165
|
-
spawn.references!(*
|
184
|
+
def references(*table_names)
|
185
|
+
check_if_method_has_arguments!(:references, table_names)
|
186
|
+
spawn.references!(*table_names)
|
166
187
|
end
|
167
188
|
|
168
|
-
def references!(*
|
169
|
-
|
189
|
+
def references!(*table_names) # :nodoc:
|
190
|
+
table_names.flatten!
|
191
|
+
table_names.map!(&:to_s)
|
170
192
|
|
171
|
-
self.references_values
|
193
|
+
self.references_values |= table_names
|
172
194
|
self
|
173
195
|
end
|
174
196
|
|
@@ -216,12 +238,16 @@ module ActiveRecord
|
|
216
238
|
to_a.select { |*block_args| yield(*block_args) }
|
217
239
|
else
|
218
240
|
raise ArgumentError, 'Call this with at least one field' if fields.empty?
|
219
|
-
spawn.
|
241
|
+
spawn._select!(*fields)
|
220
242
|
end
|
221
243
|
end
|
222
244
|
|
223
|
-
def
|
224
|
-
|
245
|
+
def _select!(*fields) # :nodoc:
|
246
|
+
fields.flatten!
|
247
|
+
fields.map! do |field|
|
248
|
+
klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
|
249
|
+
end
|
250
|
+
self.select_values += fields
|
225
251
|
self
|
226
252
|
end
|
227
253
|
|
@@ -241,7 +267,7 @@ module ActiveRecord
|
|
241
267
|
# User.group('name AS grouped_name, age')
|
242
268
|
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
|
243
269
|
def group(*args)
|
244
|
-
check_if_method_has_arguments!(
|
270
|
+
check_if_method_has_arguments!(:group, args)
|
245
271
|
spawn.group!(*args)
|
246
272
|
end
|
247
273
|
|
@@ -272,24 +298,12 @@ module ActiveRecord
|
|
272
298
|
# User.order(:name, email: :desc)
|
273
299
|
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
274
300
|
def order(*args)
|
275
|
-
check_if_method_has_arguments!(
|
301
|
+
check_if_method_has_arguments!(:order, args)
|
276
302
|
spawn.order!(*args)
|
277
303
|
end
|
278
304
|
|
279
305
|
def order!(*args) # :nodoc:
|
280
|
-
args
|
281
|
-
validate_order_args args
|
282
|
-
|
283
|
-
references = args.reject { |arg| Arel::Node === arg }
|
284
|
-
references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
|
285
|
-
references!(references) if references.any?
|
286
|
-
|
287
|
-
# if a symbol is given we prepend the quoted table name
|
288
|
-
args = args.map! { |arg|
|
289
|
-
arg.is_a?(Symbol) ?
|
290
|
-
Arel::Nodes::Ascending.new(klass.arel_table[arg]) :
|
291
|
-
arg
|
292
|
-
}
|
306
|
+
preprocess_order_args(args)
|
293
307
|
|
294
308
|
self.order_values += args
|
295
309
|
self
|
@@ -305,13 +319,12 @@ module ActiveRecord
|
|
305
319
|
#
|
306
320
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
307
321
|
def reorder(*args)
|
308
|
-
check_if_method_has_arguments!(
|
322
|
+
check_if_method_has_arguments!(:reorder, args)
|
309
323
|
spawn.reorder!(*args)
|
310
324
|
end
|
311
325
|
|
312
326
|
def reorder!(*args) # :nodoc:
|
313
|
-
args
|
314
|
-
validate_order_args args
|
327
|
+
preprocess_order_args(args)
|
315
328
|
|
316
329
|
self.reordering_value = true
|
317
330
|
self.order_values = args
|
@@ -342,20 +355,27 @@ module ActiveRecord
|
|
342
355
|
# User.where(name: "John", active: true).unscope(where: :name)
|
343
356
|
# == User.where(active: true)
|
344
357
|
#
|
345
|
-
#
|
346
|
-
#
|
347
|
-
#
|
358
|
+
# This method is similar to <tt>except</tt>, but unlike
|
359
|
+
# <tt>except</tt>, it persists across merges:
|
360
|
+
#
|
361
|
+
# User.order('email').merge(User.except(:order))
|
362
|
+
# == User.order('email')
|
363
|
+
#
|
364
|
+
# User.order('email').merge(User.unscope(:order))
|
365
|
+
# == User.all
|
348
366
|
#
|
349
|
-
#
|
367
|
+
# This means it can be used in association definitions:
|
368
|
+
#
|
369
|
+
# has_many :comments, -> { unscope where: :trashed }
|
350
370
|
#
|
351
|
-
# will still have an order if it comes from the default_scope on Comment.
|
352
371
|
def unscope(*args)
|
353
|
-
check_if_method_has_arguments!(
|
372
|
+
check_if_method_has_arguments!(:unscope, args)
|
354
373
|
spawn.unscope!(*args)
|
355
374
|
end
|
356
375
|
|
357
376
|
def unscope!(*args) # :nodoc:
|
358
377
|
args.flatten!
|
378
|
+
self.unscope_values += args
|
359
379
|
|
360
380
|
args.each do |scope|
|
361
381
|
case scope
|
@@ -389,8 +409,12 @@ module ActiveRecord
|
|
389
409
|
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
|
390
410
|
# => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
391
411
|
def joins(*args)
|
392
|
-
check_if_method_has_arguments!(
|
393
|
-
|
412
|
+
check_if_method_has_arguments!(:joins, args)
|
413
|
+
|
414
|
+
args.compact!
|
415
|
+
args.flatten!
|
416
|
+
|
417
|
+
spawn.joins!(*args)
|
394
418
|
end
|
395
419
|
|
396
420
|
def joins!(*args) # :nodoc:
|
@@ -417,7 +441,7 @@ module ActiveRecord
|
|
417
441
|
# === string
|
418
442
|
#
|
419
443
|
# A single string, without additional arguments, is passed to the query
|
420
|
-
# constructor as
|
444
|
+
# constructor as an SQL fragment, and used in the where clause of the query.
|
421
445
|
#
|
422
446
|
# Client.where("orders_count = '2'")
|
423
447
|
# # SELECT * from clients where orders_count = '2';
|
@@ -540,13 +564,28 @@ module ActiveRecord
|
|
540
564
|
if opts == :chain
|
541
565
|
WhereChain.new(self)
|
542
566
|
else
|
543
|
-
|
567
|
+
if Hash === opts
|
568
|
+
opts = sanitize_forbidden_attributes(opts)
|
569
|
+
references!(PredicateBuilder.references(opts))
|
570
|
+
end
|
544
571
|
|
545
572
|
self.where_values += build_where(opts, rest)
|
546
573
|
self
|
547
574
|
end
|
548
575
|
end
|
549
576
|
|
577
|
+
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
|
578
|
+
#
|
579
|
+
# Post.where(trashed: true).where(trashed: false) # => WHERE `trashed` = 1 AND `trashed` = 0
|
580
|
+
# Post.where(trashed: true).rewhere(trashed: false) # => WHERE `trashed` = 0
|
581
|
+
# Post.where(active: true).where(trashed: true).rewhere(trashed: false) # => WHERE `active` = 1 AND `trashed` = 0
|
582
|
+
#
|
583
|
+
# This is short-hand for unscope(where: conditions.keys).where(conditions). Note that unlike reorder, we're only unscoping
|
584
|
+
# the named conditions -- not the entire where statement.
|
585
|
+
def rewhere(conditions)
|
586
|
+
unscope(where: conditions.keys).where(conditions)
|
587
|
+
end
|
588
|
+
|
550
589
|
# Allows to specify a HAVING clause. Note that you can't use HAVING
|
551
590
|
# without also specifying a GROUP clause.
|
552
591
|
#
|
@@ -633,16 +672,16 @@ module ActiveRecord
|
|
633
672
|
# when 'Reviewer'
|
634
673
|
# Post.published
|
635
674
|
# when 'Bad User'
|
636
|
-
# Post.none #
|
675
|
+
# Post.none # It can't be chained if [] is returned.
|
637
676
|
# end
|
638
677
|
# end
|
639
678
|
#
|
640
679
|
def none
|
641
|
-
extending(NullRelation)
|
680
|
+
where("1=0").extending!(NullRelation)
|
642
681
|
end
|
643
682
|
|
644
683
|
def none! # :nodoc:
|
645
|
-
extending!(NullRelation)
|
684
|
+
where!("1=0").extending!(NullRelation)
|
646
685
|
end
|
647
686
|
|
648
687
|
# Sets readonly attributes for the returned relation. If value is
|
@@ -678,14 +717,20 @@ module ActiveRecord
|
|
678
717
|
end
|
679
718
|
|
680
719
|
def create_with!(value) # :nodoc:
|
681
|
-
|
720
|
+
if value
|
721
|
+
value = sanitize_forbidden_attributes(value)
|
722
|
+
self.create_with_value = create_with_value.merge(value)
|
723
|
+
else
|
724
|
+
self.create_with_value = {}
|
725
|
+
end
|
726
|
+
|
682
727
|
self
|
683
728
|
end
|
684
729
|
|
685
730
|
# Specifies table from which the records will be fetched. For example:
|
686
731
|
#
|
687
732
|
# Topic.select('title').from('posts')
|
688
|
-
#
|
733
|
+
# # => SELECT title FROM posts
|
689
734
|
#
|
690
735
|
# Can accept other relation objects. For example:
|
691
736
|
#
|
@@ -771,9 +816,10 @@ module ActiveRecord
|
|
771
816
|
end
|
772
817
|
|
773
818
|
def extending!(*modules, &block) # :nodoc:
|
774
|
-
modules << Module.new(&block) if
|
819
|
+
modules << Module.new(&block) if block
|
820
|
+
modules.flatten!
|
775
821
|
|
776
|
-
self.extending_values += modules
|
822
|
+
self.extending_values += modules
|
777
823
|
extend(*extending_values) if extending_values.any?
|
778
824
|
|
779
825
|
self
|
@@ -792,11 +838,12 @@ module ActiveRecord
|
|
792
838
|
end
|
793
839
|
|
794
840
|
# Returns the Arel object associated with the relation.
|
795
|
-
def arel
|
796
|
-
@arel ||=
|
841
|
+
def arel # :nodoc:
|
842
|
+
@arel ||= build_arel
|
797
843
|
end
|
798
844
|
|
799
|
-
|
845
|
+
private
|
846
|
+
|
800
847
|
def build_arel
|
801
848
|
arel = Arel::SelectManager.new(table.engine, table)
|
802
849
|
|
@@ -804,37 +851,43 @@ module ActiveRecord
|
|
804
851
|
|
805
852
|
collapse_wheres(arel, (where_values - ['']).uniq)
|
806
853
|
|
807
|
-
arel.having(*having_values.uniq.reject
|
854
|
+
arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
|
808
855
|
|
809
856
|
arel.take(connection.sanitize_limit(limit_value)) if limit_value
|
810
857
|
arel.skip(offset_value.to_i) if offset_value
|
811
|
-
|
812
|
-
arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
|
858
|
+
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
813
859
|
|
814
860
|
build_order(arel)
|
815
861
|
|
816
|
-
build_select(arel
|
862
|
+
build_select(arel)
|
817
863
|
|
818
864
|
arel.distinct(distinct_value)
|
819
865
|
arel.from(build_from) if from_value
|
820
866
|
arel.lock(lock_value) if lock_value
|
821
867
|
|
868
|
+
# Reorder bind indexes if joins produced bind values
|
869
|
+
bvs = arel.bind_values + bind_values
|
870
|
+
if bvs.any?
|
871
|
+
arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
|
872
|
+
column = bvs[i].first
|
873
|
+
bp.replace connection.substitute_at(column, i)
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
822
877
|
arel
|
823
878
|
end
|
824
879
|
|
825
|
-
private
|
826
|
-
|
827
880
|
def symbol_unscoping(scope)
|
828
881
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
829
882
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
830
883
|
end
|
831
884
|
|
832
885
|
single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
|
833
|
-
unscope_code =
|
886
|
+
unscope_code = "#{scope}_value#{'s' unless single_val_method}="
|
834
887
|
|
835
888
|
case scope
|
836
889
|
when :order
|
837
|
-
self.
|
890
|
+
self.reverse_order_value = false
|
838
891
|
result = []
|
839
892
|
else
|
840
893
|
result = [] unless single_val_method
|
@@ -844,27 +897,25 @@ module ActiveRecord
|
|
844
897
|
end
|
845
898
|
|
846
899
|
def where_unscoping(target_value)
|
847
|
-
|
900
|
+
target_value = target_value.to_s
|
848
901
|
|
849
902
|
where_values.reject! do |rel|
|
850
903
|
case rel
|
851
904
|
when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
|
852
905
|
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
|
853
|
-
subrelation.name
|
854
|
-
else
|
855
|
-
raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
|
906
|
+
subrelation.name == target_value
|
856
907
|
end
|
857
908
|
end
|
909
|
+
|
910
|
+
bind_values.reject! { |col,_| col.name == target_value }
|
858
911
|
end
|
859
912
|
|
860
913
|
def custom_join_ast(table, joins)
|
861
|
-
joins = joins.reject
|
914
|
+
joins = joins.reject(&:blank?)
|
862
915
|
|
863
916
|
return [] if joins.empty?
|
864
917
|
|
865
|
-
|
866
|
-
|
867
|
-
joins.map do |join|
|
918
|
+
joins.map! do |join|
|
868
919
|
case join
|
869
920
|
when Array
|
870
921
|
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
|
@@ -897,11 +948,10 @@ module ActiveRecord
|
|
897
948
|
|
898
949
|
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
899
950
|
when Hash
|
951
|
+
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
|
900
952
|
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
|
901
953
|
|
902
|
-
attributes
|
903
|
-
self.bind_values += rel.bind_values
|
904
|
-
end
|
954
|
+
add_relations_to_bind_values(attributes)
|
905
955
|
|
906
956
|
PredicateBuilder.build_from_hash(klass, attributes, table)
|
907
957
|
else
|
@@ -928,7 +978,7 @@ module ActiveRecord
|
|
928
978
|
:string_join
|
929
979
|
when Hash, Symbol, Array
|
930
980
|
:association_join
|
931
|
-
when ActiveRecord::Associations::JoinDependency
|
981
|
+
when ActiveRecord::Associations::JoinDependency
|
932
982
|
:stashed_join
|
933
983
|
when Arel::Nodes::Join
|
934
984
|
:join_node
|
@@ -940,7 +990,7 @@ module ActiveRecord
|
|
940
990
|
association_joins = buckets[:association_join] || []
|
941
991
|
stashed_association_joins = buckets[:stashed_join] || []
|
942
992
|
join_nodes = (buckets[:join_node] || []).uniq
|
943
|
-
string_joins = (buckets[:string_join] || []).map
|
993
|
+
string_joins = (buckets[:string_join] || []).map(&:strip).uniq
|
944
994
|
|
945
995
|
join_list = join_nodes + custom_join_ast(manager, string_joins)
|
946
996
|
|
@@ -950,29 +1000,33 @@ module ActiveRecord
|
|
950
1000
|
join_list
|
951
1001
|
)
|
952
1002
|
|
953
|
-
join_dependency.
|
1003
|
+
joins = join_dependency.join_constraints stashed_association_joins
|
954
1004
|
|
955
|
-
|
1005
|
+
joins.each { |join| manager.from(join) }
|
956
1006
|
|
957
|
-
|
958
|
-
join_dependency.join_associations.each do |association|
|
959
|
-
association.join_to(manager)
|
960
|
-
end
|
961
|
-
|
962
|
-
manager.join_sources.concat join_list
|
1007
|
+
manager.join_sources.concat(join_list)
|
963
1008
|
|
964
1009
|
manager
|
965
1010
|
end
|
966
1011
|
|
967
|
-
def build_select(arel
|
968
|
-
|
969
|
-
|
970
|
-
arel.project(*selects)
|
1012
|
+
def build_select(arel)
|
1013
|
+
if select_values.any?
|
1014
|
+
arel.project(*arel_columns(select_values.uniq))
|
971
1015
|
else
|
972
1016
|
arel.project(@klass.arel_table[Arel.star])
|
973
1017
|
end
|
974
1018
|
end
|
975
1019
|
|
1020
|
+
def arel_columns(columns)
|
1021
|
+
columns.map do |field|
|
1022
|
+
if columns_hash.key?(field.to_s)
|
1023
|
+
arel_table[field]
|
1024
|
+
else
|
1025
|
+
field
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
976
1030
|
def reverse_sql_order(order_query)
|
977
1031
|
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
|
978
1032
|
|
@@ -981,16 +1035,10 @@ module ActiveRecord
|
|
981
1035
|
when Arel::Nodes::Ordering
|
982
1036
|
o.reverse
|
983
1037
|
when String
|
984
|
-
o.to_s.split(',').
|
1038
|
+
o.to_s.split(',').map! do |s|
|
985
1039
|
s.strip!
|
986
1040
|
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
987
1041
|
end
|
988
|
-
when Symbol
|
989
|
-
{ o => :desc }
|
990
|
-
when Hash
|
991
|
-
o.each_with_object({}) do |(field, dir), memo|
|
992
|
-
memo[field] = (dir == :asc ? :desc : :asc )
|
993
|
-
end
|
994
1042
|
else
|
995
1043
|
o
|
996
1044
|
end
|
@@ -998,35 +1046,50 @@ module ActiveRecord
|
|
998
1046
|
end
|
999
1047
|
|
1000
1048
|
def array_of_strings?(o)
|
1001
|
-
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
|
1049
|
+
o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
|
1002
1050
|
end
|
1003
1051
|
|
1004
1052
|
def build_order(arel)
|
1005
|
-
orders = order_values
|
1053
|
+
orders = order_values.uniq
|
1054
|
+
orders.reject!(&:blank?)
|
1006
1055
|
orders = reverse_sql_order(orders) if reverse_order_value
|
1007
1056
|
|
1008
|
-
orders = orders.uniq.reject(&:blank?).flat_map do |order|
|
1009
|
-
case order
|
1010
|
-
when Symbol
|
1011
|
-
table[order].asc
|
1012
|
-
when Hash
|
1013
|
-
order.map { |field, dir| table[field].send(dir) }
|
1014
|
-
else
|
1015
|
-
order
|
1016
|
-
end
|
1017
|
-
end
|
1018
|
-
|
1019
1057
|
arel.order(*orders) unless orders.empty?
|
1020
1058
|
end
|
1021
1059
|
|
1022
1060
|
def validate_order_args(args)
|
1023
|
-
args.
|
1061
|
+
args.grep(Hash) do |h|
|
1024
1062
|
unless (h.values - [:asc, :desc]).empty?
|
1025
1063
|
raise ArgumentError, 'Direction should be :asc or :desc'
|
1026
1064
|
end
|
1027
1065
|
end
|
1028
1066
|
end
|
1029
1067
|
|
1068
|
+
def preprocess_order_args(order_args)
|
1069
|
+
order_args.flatten!
|
1070
|
+
validate_order_args(order_args)
|
1071
|
+
|
1072
|
+
references = order_args.grep(String)
|
1073
|
+
references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
|
1074
|
+
references!(references) if references.any?
|
1075
|
+
|
1076
|
+
# if a symbol is given we prepend the quoted table name
|
1077
|
+
order_args.map! do |arg|
|
1078
|
+
case arg
|
1079
|
+
when Symbol
|
1080
|
+
arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
|
1081
|
+
table[arg].asc
|
1082
|
+
when Hash
|
1083
|
+
arg.map { |field, dir|
|
1084
|
+
field = klass.attribute_alias(field) if klass.attribute_alias?(field)
|
1085
|
+
table[field].send(dir)
|
1086
|
+
}
|
1087
|
+
else
|
1088
|
+
arg
|
1089
|
+
end
|
1090
|
+
end.flatten!
|
1091
|
+
end
|
1092
|
+
|
1030
1093
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1031
1094
|
# blank-like object were initially passed into the query method, then this
|
1032
1095
|
# method will not raise an error.
|
@@ -1048,5 +1111,19 @@ module ActiveRecord
|
|
1048
1111
|
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1049
1112
|
end
|
1050
1113
|
end
|
1114
|
+
|
1115
|
+
# This function is recursive just for better readablity.
|
1116
|
+
# #where argument doesn't support more than one level nested hash in real world.
|
1117
|
+
def add_relations_to_bind_values(attributes)
|
1118
|
+
if attributes.is_a?(Hash)
|
1119
|
+
attributes.each_value do |value|
|
1120
|
+
if value.is_a?(ActiveRecord::Relation)
|
1121
|
+
self.bind_values += value.bind_values
|
1122
|
+
else
|
1123
|
+
add_relations_to_bind_values(value)
|
1124
|
+
end
|
1125
|
+
end
|
1126
|
+
end
|
1127
|
+
end
|
1051
1128
|
end
|
1052
1129
|
end
|
@@ -12,6 +12,7 @@ module ActiveRecord
|
|
12
12
|
|
13
13
|
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
|
14
14
|
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
|
15
|
+
#
|
15
16
|
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
|
16
17
|
# # Performs a single join query with both where conditions.
|
17
18
|
#
|
@@ -37,11 +38,14 @@ module ActiveRecord
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def merge!(other) # :nodoc:
|
40
|
-
if
|
41
|
+
if other.is_a?(Hash)
|
42
|
+
Relation::HashMerger.new(self, other).merge
|
43
|
+
elsif other.is_a?(Relation)
|
44
|
+
Relation::Merger.new(self, other).merge
|
45
|
+
elsif other.respond_to?(:to_proc)
|
41
46
|
instance_exec(&other)
|
42
47
|
else
|
43
|
-
|
44
|
-
klass.new(self, other).merge
|
48
|
+
raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
@@ -64,8 +68,7 @@ module ActiveRecord
|
|
64
68
|
private
|
65
69
|
|
66
70
|
def relation_with(values) # :nodoc:
|
67
|
-
result = Relation.
|
68
|
-
result.default_scoped = default_scoped
|
71
|
+
result = Relation.create(klass, table, values)
|
69
72
|
result.extend(*extending_values) if extending_values.any?
|
70
73
|
result
|
71
74
|
end
|