activerecord 5.0.0.beta2 → 5.0.0.beta3

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -20
  3. data/lib/active_record/association_relation.rb +1 -1
  4. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -0
  5. data/lib/active_record/associations/collection_association.rb +12 -1
  6. data/lib/active_record/associations/collection_proxy.rb +14 -0
  7. data/lib/active_record/associations/join_dependency/join_association.rb +13 -7
  8. data/lib/active_record/associations/preloader/association.rb +1 -1
  9. data/lib/active_record/associations/singular_association.rb +1 -1
  10. data/lib/active_record/attribute_assignment.rb +0 -8
  11. data/lib/active_record/attribute_methods.rb +0 -24
  12. data/lib/active_record/attribute_methods/read.rb +5 -17
  13. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -2
  14. data/lib/active_record/attribute_methods/write.rb +0 -13
  15. data/lib/active_record/base.rb +0 -1
  16. data/lib/active_record/callbacks.rb +1 -1
  17. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
  18. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  19. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -2
  20. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  21. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +4 -6
  22. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -2
  23. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +10 -16
  24. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  25. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +2 -2
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -3
  27. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  28. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -3
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +3 -4
  30. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +0 -1
  31. data/lib/active_record/core.rb +5 -0
  32. data/lib/active_record/gem_version.rb +1 -1
  33. data/lib/active_record/inheritance.rb +1 -1
  34. data/lib/active_record/migration/compatibility.rb +1 -1
  35. data/lib/active_record/nested_attributes.rb +14 -6
  36. data/lib/active_record/null_relation.rb +1 -1
  37. data/lib/active_record/querying.rb +3 -3
  38. data/lib/active_record/reflection.rb +53 -36
  39. data/lib/active_record/relation.rb +26 -18
  40. data/lib/active_record/relation/batches.rb +4 -4
  41. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  42. data/lib/active_record/relation/calculations.rb +2 -10
  43. data/lib/active_record/relation/delegation.rb +2 -1
  44. data/lib/active_record/relation/finder_methods.rb +55 -26
  45. data/lib/active_record/relation/predicate_builder.rb +3 -4
  46. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +10 -0
  47. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  48. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  49. data/lib/active_record/relation/query_methods.rb +11 -7
  50. data/lib/active_record/relation/spawn_methods.rb +1 -1
  51. data/lib/active_record/sanitization.rb +1 -1
  52. data/lib/active_record/schema_dumper.rb +6 -4
  53. data/lib/active_record/scoping/named.rb +10 -0
  54. data/lib/active_record/statement_cache.rb +1 -1
  55. data/lib/active_record/table_metadata.rb +5 -1
  56. data/lib/active_record/tasks/database_tasks.rb +4 -0
  57. data/lib/active_record/validations.rb +1 -1
  58. data/lib/active_record/validations/absence.rb +0 -1
  59. data/lib/active_record/validations/length.rb +0 -12
  60. data/lib/active_record/validations/presence.rb +0 -1
  61. data/lib/active_record/validations/uniqueness.rb +7 -9
  62. data/lib/rails/generators/active_record/model/model_generator.rb +9 -0
  63. data/lib/rails/generators/active_record/model/templates/application_record.rb +3 -0
  64. metadata +9 -8
@@ -47,7 +47,7 @@ module ActiveRecord
47
47
 
48
48
  if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
49
49
  primary_key_value = connection.next_sequence_value(klass.sequence_name)
50
- values[klass.arel_table[klass.primary_key]] = primary_key_value
50
+ values[arel_attribute(klass.primary_key)] = primary_key_value
51
51
  end
52
52
  end
53
53
 
@@ -105,6 +105,10 @@ module ActiveRecord
105
105
  [substitutes, binds]
106
106
  end
107
107
 
108
+ def arel_attribute(name) # :nodoc:
109
+ klass.arel_attribute(name, table)
110
+ end
111
+
108
112
  # Initializes new record from relation while maintaining the current
109
113
  # scope.
110
114
  #
@@ -249,17 +253,21 @@ module ActiveRecord
249
253
 
250
254
  # Converts relation objects to Array.
251
255
  def to_a
256
+ records.dup
257
+ end
258
+
259
+ def records # :nodoc:
252
260
  load
253
261
  @records
254
262
  end
255
263
 
256
264
  # Serializes the relation objects Array.
257
265
  def encode_with(coder)
258
- coder.represent_seq(nil, to_a)
266
+ coder.represent_seq(nil, records)
259
267
  end
260
268
 
261
269
  def as_json(options = nil) #:nodoc:
262
- to_a.as_json(options)
270
+ records.as_json(options)
263
271
  end
264
272
 
265
273
  # Returns size of the records.
@@ -294,13 +302,13 @@ module ActiveRecord
294
302
  # Returns true if there is exactly one record.
295
303
  def one?
296
304
  return super if block_given?
297
- limit_value ? to_a.one? : size == 1
305
+ limit_value ? records.one? : size == 1
298
306
  end
299
307
 
300
308
  # Returns true if there is more than one record.
301
309
  def many?
302
310
  return super if block_given?
303
- limit_value ? to_a.many? : size > 1
311
+ limit_value ? records.many? : size > 1
304
312
  end
305
313
 
306
314
  # Returns a cache key that can be used to identify the records fetched by
@@ -373,9 +381,9 @@ module ActiveRecord
373
381
  stmt.table(table)
374
382
 
375
383
  if joins_values.any?
376
- @klass.connection.join_to_update(stmt, arel, table[primary_key])
384
+ @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
377
385
  else
378
- stmt.key = table[primary_key]
386
+ stmt.key = arel_attribute(primary_key)
379
387
  stmt.take(arel.limit)
380
388
  stmt.order(*arel.orders)
381
389
  stmt.wheres = arel.constraints
@@ -414,7 +422,7 @@ module ActiveRecord
414
422
  if id.is_a?(Array)
415
423
  id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
416
424
  elsif id == :all
417
- to_a.each { |record| record.update(attributes) }
425
+ records.each { |record| record.update(attributes) }
418
426
  else
419
427
  if ActiveRecord::Base === id
420
428
  id = id.id
@@ -453,7 +461,7 @@ module ActiveRecord
453
461
  MESSAGE
454
462
  where(conditions).destroy_all
455
463
  else
456
- to_a.each(&:destroy).tap { reset }
464
+ records.each(&:destroy).tap { reset }
457
465
  end
458
466
  end
459
467
 
@@ -527,7 +535,7 @@ module ActiveRecord
527
535
  stmt.from(table)
528
536
 
529
537
  if joins_values.any?
530
- @klass.connection.join_to_delete(stmt, arel, table[primary_key])
538
+ @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
531
539
  else
532
540
  stmt.wheres = arel.constraints
533
541
  end
@@ -583,7 +591,7 @@ module ActiveRecord
583
591
  def reset
584
592
  @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
585
593
  @should_eager_load = @join_dependency = nil
586
- @records = []
594
+ @records = [].freeze
587
595
  @offsets = {}
588
596
  self
589
597
  end
@@ -650,21 +658,21 @@ module ActiveRecord
650
658
  def ==(other)
651
659
  case other
652
660
  when Associations::CollectionProxy, AssociationRelation
653
- self == other.to_a
661
+ self == other.records
654
662
  when Relation
655
663
  other.to_sql == to_sql
656
664
  when Array
657
- to_a == other
665
+ records == other
658
666
  end
659
667
  end
660
668
 
661
669
  def pretty_print(q)
662
- q.pp(self.to_a)
670
+ q.pp(self.records)
663
671
  end
664
672
 
665
673
  # Returns true if relation is blank.
666
674
  def blank?
667
- to_a.blank?
675
+ records.blank?
668
676
  end
669
677
 
670
678
  def values
@@ -672,7 +680,7 @@ module ActiveRecord
672
680
  end
673
681
 
674
682
  def inspect
675
- entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
683
+ entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
676
684
  entries[10] = '...' if entries.size == 11
677
685
 
678
686
  "#<#{self.class.name} [#{entries.join(', ')}]>"
@@ -681,14 +689,14 @@ module ActiveRecord
681
689
  protected
682
690
 
683
691
  def load_records(records)
684
- @records = records
692
+ @records = records.freeze
685
693
  @loaded = true
686
694
  end
687
695
 
688
696
  private
689
697
 
690
698
  def exec_queries
691
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
699
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze
692
700
 
693
701
  preload = preload_values
694
702
  preload += includes_values unless eager_loading?
@@ -187,7 +187,7 @@ module ActiveRecord
187
187
 
188
188
  loop do
189
189
  if load
190
- records = batch_relation.to_a
190
+ records = batch_relation.records
191
191
  ids = records.map(&:id)
192
192
  yielded_relation = self.where(primary_key => ids)
193
193
  yielded_relation.load_records(records)
@@ -204,15 +204,15 @@ module ActiveRecord
204
204
  yield yielded_relation
205
205
 
206
206
  break if ids.length < of
207
- batch_relation = relation.where(table[primary_key].gt(primary_key_offset))
207
+ batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
208
208
  end
209
209
  end
210
210
 
211
211
  private
212
212
 
213
213
  def apply_limits(relation, start, finish)
214
- relation = relation.where(table[primary_key].gteq(start)) if start
215
- relation = relation.where(table[primary_key].lteq(finish)) if finish
214
+ relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
215
+ relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
216
216
  relation
217
217
  end
218
218
 
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  return to_enum(:each_record) unless block_given?
36
36
 
37
37
  @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
38
- relation.to_a.each { |record| yield record }
38
+ relation.records.each { |record| yield record }
39
39
  end
40
40
  end
41
41
 
@@ -155,15 +155,7 @@ module ActiveRecord
155
155
  # See also #ids.
156
156
  #
157
157
  def pluck(*column_names)
158
- column_names.map! do |column_name|
159
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
160
- attribute_alias(column_name)
161
- else
162
- column_name.to_s
163
- end
164
- end
165
-
166
- if loaded? && (column_names - @klass.column_names).empty?
158
+ if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
167
159
  return @records.pluck(*column_names)
168
160
  end
169
161
 
@@ -172,7 +164,7 @@ module ActiveRecord
172
164
  else
173
165
  relation = spawn
174
166
  relation.select_values = column_names.map { |cn|
175
- columns_hash.key?(cn) ? arel_table[cn] : cn
167
+ @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
176
168
  }
177
169
  result = klass.connection.select_all(relation.arel, nil, bound_attributes)
178
170
  result.cast_values(klass.attribute_types)
@@ -37,7 +37,8 @@ module ActiveRecord
37
37
  # for each different klass, and the delegations are compiled into that subclass only.
38
38
 
39
39
  delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
40
- :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a
40
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
41
+ :shuffle, :split, to: :records
41
42
 
42
43
  delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
43
44
  :connection, :columns_hash, :to => :klass
@@ -145,15 +145,21 @@ module ActiveRecord
145
145
  #
146
146
  # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
147
147
  def last(limit = nil)
148
- if limit
149
- if order_values.empty? && primary_key
150
- order(arel_table[primary_key].desc).limit(limit).reverse
151
- else
152
- to_a.last(limit)
153
- end
154
- else
155
- find_last
156
- end
148
+ return find_last(limit) if loaded? || limit_value
149
+
150
+ result = limit(limit || 1)
151
+ result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
152
+ result = result.reverse_order!
153
+
154
+ limit ? result.reverse : result.first
155
+ rescue ActiveRecord::IrreversibleOrderError
156
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
157
+ Finding a last element by loading the relation when SQL ORDER
158
+ can not be reversed is deprecated.
159
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
160
+ Please call `to_a.last` if you still want to load the relation.
161
+ WARNING
162
+ find_last(limit)
157
163
  end
158
164
 
159
165
  # Same as #last but raises ActiveRecord::RecordNotFound if no record
@@ -242,6 +248,38 @@ module ActiveRecord
242
248
  find_nth! 41
243
249
  end
244
250
 
251
+ # Find the third-to-last record.
252
+ # If no order is defined it will order by primary key.
253
+ #
254
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
255
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
256
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
257
+ def third_to_last
258
+ find_nth(-3)
259
+ end
260
+
261
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
262
+ # is found.
263
+ def third_to_last!
264
+ find_nth!(-3)
265
+ end
266
+
267
+ # Find the second-to-last record.
268
+ # If no order is defined it will order by primary key.
269
+ #
270
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
271
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
272
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
273
+ def second_to_last
274
+ find_nth(-2)
275
+ end
276
+
277
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
278
+ # is found.
279
+ def second_to_last!
280
+ find_nth!(-2)
281
+ end
282
+
245
283
  # Returns true if a record exists in the table that matches the +id+ or
246
284
  # conditions given, or false otherwise. The argument can take six forms:
247
285
  #
@@ -298,7 +336,7 @@ module ActiveRecord
298
336
  end
299
337
 
300
338
  # This method is called whenever no records are found with either a single
301
- # id or multiple ids and raises a ActiveRecord::RecordNotFound exception.
339
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
302
340
  #
303
341
  # The error message is different depending on whether a single id or
304
342
  # multiple ids are provided. If multiple ids are provided, then the number
@@ -468,7 +506,7 @@ module ActiveRecord
468
506
  def find_some_ordered(ids)
469
507
  ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
470
508
 
471
- result = except(:limit, :offset).where(primary_key => ids).to_a
509
+ result = except(:limit, :offset).where(primary_key => ids).records
472
510
 
473
511
  if result.size == ids.size
474
512
  pk_type = @klass.type_for_attribute(primary_key)
@@ -484,7 +522,7 @@ module ActiveRecord
484
522
  if loaded?
485
523
  @records.first
486
524
  else
487
- @take ||= limit(1).to_a.first
525
+ @take ||= limit(1).records.first
488
526
  end
489
527
  end
490
528
 
@@ -514,7 +552,7 @@ module ActiveRecord
514
552
  # TODO: once the offset argument is removed from find_nth,
515
553
  # find_nth_with_limit_and_offset can be merged into this method
516
554
  relation = if order_values.empty? && primary_key
517
- order(arel_table[primary_key].asc)
555
+ order(arel_attribute(primary_key).asc)
518
556
  else
519
557
  self
520
558
  end
@@ -523,19 +561,6 @@ module ActiveRecord
523
561
  relation.limit(limit).to_a
524
562
  end
525
563
 
526
- def find_last
527
- if loaded?
528
- @records.last
529
- else
530
- @last ||=
531
- if limit_value
532
- to_a.last
533
- else
534
- reverse_order.limit(1).to_a.first
535
- end
536
- end
537
- end
538
-
539
564
  private
540
565
 
541
566
  def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
@@ -546,5 +571,9 @@ module ActiveRecord
546
571
  find_nth_with_limit(index, limit)
547
572
  end
548
573
  end
574
+
575
+ def find_last(limit)
576
+ limit ? records.last(limit) : records.last
577
+ end
549
578
  end
550
579
  end
@@ -5,6 +5,7 @@ module ActiveRecord
5
5
  require 'active_record/relation/predicate_builder/base_handler'
6
6
  require 'active_record/relation/predicate_builder/basic_object_handler'
7
7
  require 'active_record/relation/predicate_builder/class_handler'
8
+ require 'active_record/relation/predicate_builder/polymorphic_array_handler'
8
9
  require 'active_record/relation/predicate_builder/range_handler'
9
10
  require 'active_record/relation/predicate_builder/relation_handler'
10
11
 
@@ -22,6 +23,7 @@ module ActiveRecord
22
23
  register_handler(Relation, RelationHandler.new)
23
24
  register_handler(Array, ArrayHandler.new(self))
24
25
  register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
26
+ register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
25
27
  end
26
28
 
27
29
  def build_from_hash(attributes)
@@ -40,10 +42,7 @@ module ActiveRecord
40
42
  #
41
43
  # For polymorphic relationships, find the foreign key and type:
42
44
  # PriceEstimate.where(estimate_of: treasure)
43
- if table.associated_with?(column)
44
- value = AssociationQueryValue.new(table.associated_table(column), value)
45
- end
46
-
45
+ value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
47
46
  build(table.arel_attribute(column), value)
48
47
  end
49
48
 
@@ -1,6 +1,16 @@
1
1
  module ActiveRecord
2
2
  class PredicateBuilder
3
3
  class AssociationQueryHandler # :nodoc:
4
+ def self.value_for(table, column, value)
5
+ klass = if table.associated_table(column).polymorphic_association? && ::Array === value && value.first.is_a?(Base)
6
+ PolymorphicArrayValue
7
+ else
8
+ AssociationQueryValue
9
+ end
10
+
11
+ klass.new(table.associated_table(column), value)
12
+ end
13
+
4
14
  def initialize(predicate_builder)
5
15
  @predicate_builder = predicate_builder
6
16
  end
@@ -0,0 +1,57 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class PolymorphicArrayHandler # :nodoc:
4
+ def initialize(predicate_builder)
5
+ @predicate_builder = predicate_builder
6
+ end
7
+
8
+ def call(attribute, value)
9
+ table = value.associated_table
10
+ queries = value.type_to_ids_mapping.map do |type, ids|
11
+ { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
12
+ end
13
+
14
+ predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
15
+
16
+ if predicates.size > 1
17
+ type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
18
+ type_and_ids_predicates.inject(&:or)
19
+ else
20
+ predicates.first
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ attr_reader :predicate_builder
27
+ end
28
+
29
+ class PolymorphicArrayValue # :nodoc:
30
+ attr_reader :associated_table, :values
31
+
32
+ def initialize(associated_table, values)
33
+ @associated_table = associated_table
34
+ @values = values
35
+ end
36
+
37
+ def type_to_ids_mapping
38
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
39
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
40
+ end
41
+
42
+ private
43
+
44
+ def primary_key(value)
45
+ associated_table.association_primary_key(base_class(value))
46
+ end
47
+
48
+ def base_class(value)
49
+ value.class.base_class
50
+ end
51
+
52
+ def convert_to_id(value)
53
+ value._read_attribute(primary_key(value))
54
+ end
55
+ end
56
+ end
57
+ end