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.

Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1632 -1797
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/examples/performance.rb +30 -18
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +4 -0
  9. data/lib/active_record/associations/alias_tracker.rb +49 -29
  10. data/lib/active_record/associations/association.rb +9 -17
  11. data/lib/active_record/associations/association_scope.rb +59 -49
  12. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
  14. data/lib/active_record/associations/builder/association.rb +84 -54
  15. data/lib/active_record/associations/builder/belongs_to.rb +90 -58
  16. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
  18. data/lib/active_record/associations/builder/has_many.rb +3 -3
  19. data/lib/active_record/associations/builder/has_one.rb +5 -7
  20. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  21. data/lib/active_record/associations/collection_association.rb +121 -111
  22. data/lib/active_record/associations/collection_proxy.rb +73 -18
  23. data/lib/active_record/associations/has_many_association.rb +14 -11
  24. data/lib/active_record/associations/has_many_through_association.rb +33 -6
  25. data/lib/active_record/associations/has_one_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
  27. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  28. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  29. data/lib/active_record/associations/join_dependency.rb +208 -168
  30. data/lib/active_record/associations/preloader/association.rb +69 -27
  31. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  35. data/lib/active_record/associations/preloader.rb +63 -49
  36. data/lib/active_record/associations/singular_association.rb +6 -5
  37. data/lib/active_record/associations/through_association.rb +30 -9
  38. data/lib/active_record/associations.rb +116 -42
  39. data/lib/active_record/attribute_assignment.rb +6 -3
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  41. data/lib/active_record/attribute_methods/dirty.rb +35 -26
  42. data/lib/active_record/attribute_methods/primary_key.rb +8 -1
  43. data/lib/active_record/attribute_methods/read.rb +56 -29
  44. data/lib/active_record/attribute_methods/serialization.rb +44 -12
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
  46. data/lib/active_record/attribute_methods/write.rb +59 -26
  47. data/lib/active_record/attribute_methods.rb +82 -43
  48. data/lib/active_record/autosave_association.rb +209 -194
  49. data/lib/active_record/base.rb +6 -2
  50. data/lib/active_record/callbacks.rb +2 -2
  51. data/lib/active_record/coders/json.rb +13 -0
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
  76. data/lib/active_record/connection_handling.rb +39 -5
  77. data/lib/active_record/core.rb +38 -54
  78. data/lib/active_record/counter_cache.rb +9 -10
  79. data/lib/active_record/dynamic_matchers.rb +6 -2
  80. data/lib/active_record/enum.rb +199 -0
  81. data/lib/active_record/errors.rb +22 -5
  82. data/lib/active_record/fixture_set/file.rb +2 -1
  83. data/lib/active_record/fixtures.rb +173 -76
  84. data/lib/active_record/gem_version.rb +15 -0
  85. data/lib/active_record/inheritance.rb +23 -9
  86. data/lib/active_record/integration.rb +54 -1
  87. data/lib/active_record/locking/optimistic.rb +7 -2
  88. data/lib/active_record/locking/pessimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +6 -13
  90. data/lib/active_record/migration/command_recorder.rb +8 -2
  91. data/lib/active_record/migration.rb +91 -56
  92. data/lib/active_record/model_schema.rb +7 -14
  93. data/lib/active_record/nested_attributes.rb +25 -13
  94. data/lib/active_record/no_touching.rb +52 -0
  95. data/lib/active_record/null_relation.rb +26 -6
  96. data/lib/active_record/persistence.rb +23 -29
  97. data/lib/active_record/querying.rb +15 -12
  98. data/lib/active_record/railtie.rb +12 -61
  99. data/lib/active_record/railties/databases.rake +37 -56
  100. data/lib/active_record/readonly_attributes.rb +0 -6
  101. data/lib/active_record/reflection.rb +230 -79
  102. data/lib/active_record/relation/batches.rb +74 -24
  103. data/lib/active_record/relation/calculations.rb +52 -48
  104. data/lib/active_record/relation/delegation.rb +54 -39
  105. data/lib/active_record/relation/finder_methods.rb +210 -67
  106. data/lib/active_record/relation/merger.rb +15 -12
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
  109. data/lib/active_record/relation/predicate_builder.rb +81 -40
  110. data/lib/active_record/relation/query_methods.rb +185 -108
  111. data/lib/active_record/relation/spawn_methods.rb +8 -5
  112. data/lib/active_record/relation.rb +79 -84
  113. data/lib/active_record/result.rb +45 -6
  114. data/lib/active_record/runtime_registry.rb +5 -0
  115. data/lib/active_record/sanitization.rb +4 -4
  116. data/lib/active_record/schema_dumper.rb +18 -6
  117. data/lib/active_record/schema_migration.rb +31 -18
  118. data/lib/active_record/scoping/default.rb +5 -18
  119. data/lib/active_record/scoping/named.rb +14 -29
  120. data/lib/active_record/scoping.rb +5 -0
  121. data/lib/active_record/store.rb +67 -18
  122. data/lib/active_record/tasks/database_tasks.rb +66 -26
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
  124. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  125. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  126. data/lib/active_record/timestamp.rb +6 -6
  127. data/lib/active_record/transactions.rb +10 -12
  128. data/lib/active_record/validations/presence.rb +1 -1
  129. data/lib/active_record/validations/uniqueness.rb +19 -9
  130. data/lib/active_record/version.rb +4 -7
  131. data/lib/active_record.rb +5 -7
  132. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  133. data/lib/rails/generators/active_record/migration.rb +18 -0
  134. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  135. data/lib/rails/generators/active_record.rb +2 -8
  136. metadata +18 -30
  137. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  138. data/lib/active_record/associations/join_helper.rb +0 -45
  139. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  141. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  142. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  143. 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!("includes", args)
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! {|a| a.blank? }
137
+ args.reject!(&:blank?)
138
+ args.flatten!
120
139
 
121
- self.includes_values = (includes_values + args).flatten.uniq
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!("eager_load", args)
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!("preload", args)
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
- # Used to indicate that an association is referenced by an SQL string, and should
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(*args)
164
- check_if_method_has_arguments!("references", args)
165
- spawn.references!(*args)
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!(*args) # :nodoc:
169
- args.flatten!
189
+ def references!(*table_names) # :nodoc:
190
+ table_names.flatten!
191
+ table_names.map!(&:to_s)
170
192
 
171
- self.references_values = (references_values + args.map!(&:to_s)).uniq
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.select!(*fields)
241
+ spawn._select!(*fields)
220
242
  end
221
243
  end
222
244
 
223
- def select!(*fields) # :nodoc:
224
- self.select_values += fields.flatten
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!("group", args)
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!("order", args)
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.flatten!
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!("reorder", args)
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.flatten!
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
- # Note that this method is more generalized than ActiveRecord::SpawnMethods#except
346
- # because #except will only affect a particular relation's values. It won't wipe
347
- # the order, grouping, etc. when that relation is merged. For example:
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
- # Post.comments.except(:order)
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!("unscope", args)
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!("joins", args)
393
- spawn.joins!(*args.compact.flatten)
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 a SQL fragment, and used in the where clause of the query.
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
- references!(PredicateBuilder.references(opts)) if Hash === opts
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 # => returning [] instead breaks the previous code
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
- self.create_with_value = value ? create_with_value.merge(value) : {}
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
- # #=> SELECT title FROM posts
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 block_given?
819
+ modules << Module.new(&block) if block
820
+ modules.flatten!
775
821
 
776
- self.extending_values += modules.flatten
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 ||= with_default_scope.build_arel
841
+ def arel # :nodoc:
842
+ @arel ||= build_arel
797
843
  end
798
844
 
799
- # Like #arel, but ignores the default scope of the model.
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{|h| h.blank?}) unless having_values.empty?
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, select_values.uniq)
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 = :"#{scope}_value#{'s' unless single_val_method}="
886
+ unscope_code = "#{scope}_value#{'s' unless single_val_method}="
834
887
 
835
888
  case scope
836
889
  when :order
837
- self.send(:reverse_order_value=, false)
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
- target_value_sym = target_value.to_sym
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.to_sym == target_value_sym
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 { |join| join.blank? }
914
+ joins = joins.reject(&:blank?)
862
915
 
863
916
  return [] if joins.empty?
864
917
 
865
- @implicit_readonly = true
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.values.grep(ActiveRecord::Relation) do |rel|
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::JoinAssociation
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 { |x| x.strip }.uniq
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.graft(*stashed_association_joins)
1003
+ joins = join_dependency.join_constraints stashed_association_joins
954
1004
 
955
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
1005
+ joins.each { |join| manager.from(join) }
956
1006
 
957
- # FIXME: refactor this to build an AST
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, selects)
968
- unless selects.empty?
969
- @implicit_readonly = false
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(',').collect do |s|
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.select { |a| Hash === a }.each do |h|
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 !other.is_a?(Relation) && other.respond_to?(:to_proc)
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
- klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
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.new(klass, table, values)
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