activerecord 5.2.1 → 5.2.5

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +228 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations/association.rb +8 -0
  5. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  6. data/lib/active_record/associations/collection_association.rb +9 -8
  7. data/lib/active_record/associations/collection_proxy.rb +8 -34
  8. data/lib/active_record/associations/has_many_association.rb +9 -0
  9. data/lib/active_record/associations/has_many_through_association.rb +28 -11
  10. data/lib/active_record/associations/join_dependency/join_association.rb +28 -7
  11. data/lib/active_record/associations/preloader.rb +1 -1
  12. data/lib/active_record/attribute_methods/dirty.rb +13 -8
  13. data/lib/active_record/autosave_association.rb +25 -11
  14. data/lib/active_record/callbacks.rb +1 -1
  15. data/lib/active_record/collection_cache_key.rb +2 -2
  16. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +36 -11
  17. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  18. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  19. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
  21. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  22. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -8
  23. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  24. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  25. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +7 -1
  26. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  27. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  28. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +10 -24
  29. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  30. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  31. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
  32. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
  33. data/lib/active_record/core.rb +2 -1
  34. data/lib/active_record/enum.rb +1 -0
  35. data/lib/active_record/errors.rb +18 -12
  36. data/lib/active_record/gem_version.rb +1 -1
  37. data/lib/active_record/migration.rb +1 -1
  38. data/lib/active_record/migration/compatibility.rb +15 -15
  39. data/lib/active_record/model_schema.rb +1 -1
  40. data/lib/active_record/persistence.rb +5 -4
  41. data/lib/active_record/querying.rb +1 -1
  42. data/lib/active_record/railtie.rb +1 -3
  43. data/lib/active_record/reflection.rb +10 -14
  44. data/lib/active_record/relation.rb +26 -7
  45. data/lib/active_record/relation/calculations.rb +16 -12
  46. data/lib/active_record/relation/delegation.rb +30 -0
  47. data/lib/active_record/relation/finder_methods.rb +8 -4
  48. data/lib/active_record/relation/merger.rb +8 -5
  49. data/lib/active_record/relation/predicate_builder.rb +14 -9
  50. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  51. data/lib/active_record/relation/query_attribute.rb +5 -3
  52. data/lib/active_record/relation/query_methods.rb +35 -10
  53. data/lib/active_record/relation/spawn_methods.rb +1 -1
  54. data/lib/active_record/scoping/default.rb +2 -2
  55. data/lib/active_record/scoping/named.rb +2 -0
  56. data/lib/active_record/statement_cache.rb +2 -2
  57. data/lib/active_record/tasks/database_tasks.rb +1 -1
  58. data/lib/active_record/transactions.rb +1 -1
  59. metadata +9 -10
@@ -99,6 +99,7 @@ module ActiveRecord
99
99
  def delete_or_nullify_all_records(method)
100
100
  count = delete_count(method, scope)
101
101
  update_counter(-count)
102
+ count
102
103
  end
103
104
 
104
105
  # Deletes the records according to the <tt>:dependent</tt> option.
@@ -130,6 +131,14 @@ module ActiveRecord
130
131
  end
131
132
  saved_successfully
132
133
  end
134
+
135
+ def difference(a, b)
136
+ a - b
137
+ end
138
+
139
+ def intersection(a, b)
140
+ a & b
141
+ end
133
142
  end
134
143
  end
135
144
  end
@@ -57,21 +57,14 @@ module ActiveRecord
57
57
  @through_records[record.object_id] ||= begin
58
58
  ensure_mutable
59
59
 
60
- through_record = through_association.build(*options_for_through_record)
61
- through_record.send("#{source_reflection.name}=", record)
60
+ attributes = through_scope_attributes
61
+ attributes[source_reflection.name] = record
62
+ attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
62
63
 
63
- if options[:source_type]
64
- through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
65
- end
66
-
67
- through_record
64
+ through_association.build(attributes)
68
65
  end
69
66
  end
70
67
 
71
- def options_for_through_record
72
- [through_scope_attributes]
73
- end
74
-
75
68
  def through_scope_attributes
76
69
  scope.where_values_hash(through_association.reflection.name.to_s).
77
70
  except!(through_association.reflection.foreign_key,
@@ -161,6 +154,30 @@ module ActiveRecord
161
154
  else
162
155
  update_counter(-count)
163
156
  end
157
+
158
+ count
159
+ end
160
+
161
+ def difference(a, b)
162
+ distribution = distribution(b)
163
+
164
+ a.reject { |record| mark_occurrence(distribution, record) }
165
+ end
166
+
167
+ def intersection(a, b)
168
+ distribution = distribution(b)
169
+
170
+ a.select { |record| mark_occurrence(distribution, record) }
171
+ end
172
+
173
+ def mark_occurrence(distribution, record)
174
+ distribution[record] > 0 && distribution[record] -= 1
175
+ end
176
+
177
+ def distribution(array)
178
+ array.each_with_object(Hash.new(0)) do |record, distribution|
179
+ distribution[record] += 1
180
+ end
164
181
  end
165
182
 
166
183
  def through_records_for(record)
@@ -30,17 +30,21 @@ module ActiveRecord
30
30
  table = tables[-i]
31
31
  klass = reflection.klass
32
32
 
33
- constraint = reflection.build_join_constraint(table, foreign_table)
33
+ join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
34
34
 
35
- joins << table.create_join(table, table.create_on(constraint), join_type)
36
-
37
- join_scope = reflection.join_scope(table, foreign_klass)
38
35
  arel = join_scope.arel(alias_tracker.aliases)
36
+ nodes = arel.constraints.first
37
+
38
+ others, children = nodes.children.partition do |node|
39
+ !fetch_arel_attribute(node) { |attr| attr.relation.name == table.name }
40
+ end
41
+ nodes = table.create_and(children)
39
42
 
40
- if arel.constraints.any?
43
+ joins << table.create_join(table, table.create_on(nodes), join_type)
44
+
45
+ unless others.empty?
41
46
  joins.concat arel.join_sources
42
- right = joins.last.right
43
- right.expr = right.expr.and(arel.constraints)
47
+ append_constraints(joins.last, others)
44
48
  end
45
49
 
46
50
  # The current table in this iteration becomes the foreign table in the next
@@ -54,6 +58,23 @@ module ActiveRecord
54
58
  @tables = tables
55
59
  @table = tables.first
56
60
  end
61
+
62
+ private
63
+ def fetch_arel_attribute(value)
64
+ case value
65
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
66
+ yield value.left.is_a?(Arel::Attributes::Attribute) ? value.left : value.right
67
+ end
68
+ end
69
+
70
+ def append_constraints(join, constraints)
71
+ if join.is_a?(Arel::Nodes::StringJoin)
72
+ join_string = table.create_and(constraints.unshift(join.left))
73
+ join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
74
+ else
75
+ join.right.expr.children.concat(constraints)
76
+ end
77
+ end
57
78
  end
58
79
  end
59
80
  end
@@ -177,7 +177,7 @@ module ActiveRecord
177
177
  # and attach it to a relation. The class returned implements a `run` method
178
178
  # that accepts a preloader.
179
179
  def preloader_for(reflection, owners)
180
- if owners.first.association(reflection.name).loaded?
180
+ if owners.all? { |o| o.association(reflection.name).loaded? }
181
181
  return AlreadyLoaded
182
182
  end
183
183
  reflection.check_preloadable!
@@ -16,9 +16,6 @@ module ActiveRecord
16
16
 
17
17
  class_attribute :partial_writes, instance_writer: false, default: true
18
18
 
19
- after_create { changes_applied }
20
- after_update { changes_applied }
21
-
22
19
  # Attribute methods for "changed in last call to save?"
23
20
  attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
24
21
  attribute_method_prefix("saved_change_to_")
@@ -123,18 +120,26 @@ module ActiveRecord
123
120
  end
124
121
 
125
122
  private
126
- def write_attribute_without_type_cast(attr_name, _)
127
- result = super
128
- clear_attribute_change(attr_name)
123
+ def write_attribute_without_type_cast(attr_name, value)
124
+ name = attr_name.to_s
125
+ if self.class.attribute_alias?(name)
126
+ name = self.class.attribute_alias(name)
127
+ end
128
+ result = super(name, value)
129
+ clear_attribute_change(name)
129
130
  result
130
131
  end
131
132
 
132
133
  def _update_record(*)
133
- partial_writes? ? super(keys_for_partial_write) : super
134
+ affected_rows = partial_writes? ? super(keys_for_partial_write) : super
135
+ changes_applied
136
+ affected_rows
134
137
  end
135
138
 
136
139
  def _create_record(*)
137
- partial_writes? ? super(keys_for_partial_write) : super
140
+ id = partial_writes? ? super(keys_for_partial_write) : super
141
+ changes_applied
142
+ id
138
143
  end
139
144
 
140
145
  def keys_for_partial_write
@@ -272,7 +272,7 @@ module ActiveRecord
272
272
  # or saved. If +autosave+ is +false+ only new records will be returned,
273
273
  # unless the parent is/was a new record itself.
274
274
  def associated_records_to_validate_or_save(association, new_record, autosave)
275
- if new_record
275
+ if new_record || custom_validation_context?
276
276
  association && association.target
277
277
  elsif autosave
278
278
  association.target.find_all(&:changed_for_autosave?)
@@ -304,7 +304,7 @@ module ActiveRecord
304
304
  def validate_single_association(reflection)
305
305
  association = association_instance_get(reflection.name)
306
306
  record = association && association.reader
307
- association_valid?(reflection, record) if record
307
+ association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
308
308
  end
309
309
 
310
310
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,7 +324,7 @@ module ActiveRecord
324
324
  def association_valid?(reflection, record, index = nil)
325
325
  return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
326
326
 
327
- context = validation_context unless [:create, :update].include?(validation_context)
327
+ context = validation_context if custom_validation_context?
328
328
 
329
329
  unless valid = record.valid?(context)
330
330
  if reflection.options[:autosave]
@@ -382,30 +382,34 @@ module ActiveRecord
382
382
  if association = association_instance_get(reflection.name)
383
383
  autosave = reflection.options[:autosave]
384
384
 
385
+ # By saving the instance variable in a local variable,
386
+ # we make the whole callback re-entrant.
387
+ new_record_before_save = @new_record_before_save
388
+
385
389
  # reconstruct the scope now that we know the owner's id
386
390
  association.reset_scope
387
391
 
388
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
392
+ if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
389
393
  if autosave
390
394
  records_to_destroy = records.select(&:marked_for_destruction?)
391
395
  records_to_destroy.each { |record| association.destroy(record) }
392
396
  records -= records_to_destroy
393
397
  end
394
398
 
395
- records.each_with_index do |record, index|
399
+ records.each do |record|
396
400
  next if record.destroyed?
397
401
 
398
402
  saved = true
399
403
 
400
- if autosave != false && (@new_record_before_save || record.new_record?)
404
+ if autosave != false && (new_record_before_save || record.new_record?)
401
405
  if autosave
402
406
  saved = association.insert_record(record, false)
403
407
  elsif !reflection.nested?
408
+ association_saved = association.insert_record(record)
409
+
404
410
  if reflection.validate?
405
- valid = association_valid?(reflection, record, index)
406
- saved = valid ? association.insert_record(record, false) : false
407
- else
408
- association.insert_record(record)
411
+ errors.add(reflection.name) unless association_saved
412
+ saved = association_saved
409
413
  end
410
414
  end
411
415
  elsif autosave
@@ -457,10 +461,16 @@ module ActiveRecord
457
461
  # If the record is new or it has changed, returns true.
458
462
  def record_changed?(reflection, record, key)
459
463
  record.new_record? ||
460
- (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
464
+ association_foreign_key_changed?(reflection, record, key) ||
461
465
  record.will_save_change_to_attribute?(reflection.foreign_key)
462
466
  end
463
467
 
468
+ def association_foreign_key_changed?(reflection, record, key)
469
+ return false if reflection.through_reflection?
470
+
471
+ record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
472
+ end
473
+
464
474
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
465
475
  #
466
476
  # In addition, it will destroy the association if it was marked for destruction.
@@ -489,6 +499,10 @@ module ActiveRecord
489
499
  end
490
500
  end
491
501
 
502
+ def custom_validation_context?
503
+ validation_context && [:create, :update].exclude?(validation_context)
504
+ end
505
+
492
506
  def _ensure_no_duplicate_errors
493
507
  errors.messages.each_key do |attribute|
494
508
  errors[attribute].uniq!
@@ -332,7 +332,7 @@ module ActiveRecord
332
332
  _run_touch_callbacks { super }
333
333
  end
334
334
 
335
- def increment!(*, touch: nil) # :nodoc:
335
+ def increment!(attribute, by = 1, touch: nil) # :nodoc:
336
336
  touch ? _run_touch_callbacks { super } : super
337
337
  end
338
338
 
@@ -20,9 +20,9 @@ module ActiveRecord
20
20
  select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
21
21
 
22
22
  if collection.has_limit_or_offset?
23
- query = collection.select(column)
23
+ query = collection.select("#{column} AS collection_cache_key_timestamp")
24
24
  subquery_alias = "subquery_for_cache_key"
25
- subquery_column = "#{subquery_alias}.#{timestamp_column}"
25
+ subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
26
26
  subquery = query.arel.as(subquery_alias)
27
27
  arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
28
28
  else
@@ -188,7 +188,9 @@ module ActiveRecord
188
188
  t0 = Time.now
189
189
  elapsed = 0
190
190
  loop do
191
- @cond.wait(timeout - elapsed)
191
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
192
+ @cond.wait(timeout - elapsed)
193
+ end
192
194
 
193
195
  return remove if any?
194
196
 
@@ -308,7 +310,7 @@ module ActiveRecord
308
310
  include QueryCache::ConnectionPoolConfiguration
309
311
 
310
312
  attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
311
- attr_reader :spec, :connections, :size, :reaper
313
+ attr_reader :spec, :size, :reaper
312
314
 
313
315
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
314
316
  # object which describes database connection information (e.g. adapter,
@@ -377,7 +379,7 @@ module ActiveRecord
377
379
  # #connection can be called any number of times; the connection is
378
380
  # held in a cache keyed by a thread.
379
381
  def connection
380
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
382
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
381
383
  end
382
384
 
383
385
  # Returns true if there is an open connection being used for the current thread.
@@ -386,7 +388,7 @@ module ActiveRecord
386
388
  # #connection or #with_connection methods. Connections obtained through
387
389
  # #checkout will not be detected by #active_connection?
388
390
  def active_connection?
389
- @thread_cached_conns[connection_cache_key(Thread.current)]
391
+ @thread_cached_conns[connection_cache_key(current_thread)]
390
392
  end
391
393
 
392
394
  # Signal that the thread is finished with the current connection.
@@ -421,6 +423,21 @@ module ActiveRecord
421
423
  synchronize { @connections.any? }
422
424
  end
423
425
 
426
+ # Returns an array containing the connections currently in the pool.
427
+ # Access to the array does not require synchronization on the pool because
428
+ # the array is newly created and not retained by the pool.
429
+ #
430
+ # However; this method bypasses the ConnectionPool's thread-safe connection
431
+ # access pattern. A returned connection may be owned by another thread,
432
+ # unowned, or by happen-stance owned by the calling thread.
433
+ #
434
+ # Calling methods on a connection without ownership is subject to the
435
+ # thread-safety guarantees of the underlying method. Many of the methods
436
+ # on connection adapter classes are inherently multi-thread unsafe.
437
+ def connections
438
+ synchronize { @connections.dup }
439
+ end
440
+
424
441
  # Disconnects all connections in the pool, and clears the pool.
425
442
  #
426
443
  # Raises:
@@ -666,6 +683,10 @@ module ActiveRecord
666
683
  thread
667
684
  end
668
685
 
686
+ def current_thread
687
+ @lock_thread || Thread.current
688
+ end
689
+
669
690
  # Take control of all existing connections so a "group" action such as
670
691
  # reload/disconnect can be performed safely. It is no longer enough to
671
692
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -913,6 +934,16 @@ module ActiveRecord
913
934
  # about the model. The model needs to pass a specification name to the handler,
914
935
  # in order to look up the correct connection pool.
915
936
  class ConnectionHandler
937
+ def self.create_owner_to_pool # :nodoc:
938
+ Concurrent::Map.new(initial_capacity: 2) do |h, k|
939
+ # Discard the parent's connection pools immediately; we have no need
940
+ # of them
941
+ discard_unowned_pools(h)
942
+
943
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
944
+ end
945
+ end
946
+
916
947
  def self.unowned_pool_finalizer(pid_map) # :nodoc:
917
948
  lambda do |_|
918
949
  discard_unowned_pools(pid_map)
@@ -927,13 +958,7 @@ module ActiveRecord
927
958
 
928
959
  def initialize
929
960
  # These caches are keyed by spec.name (ConnectionSpecification#name).
930
- @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
931
- # Discard the parent's connection pools immediately; we have no need
932
- # of them
933
- ConnectionHandler.discard_unowned_pools(h)
934
-
935
- h[k] = Concurrent::Map.new(initial_capacity: 2)
936
- end
961
+ @owner_to_pool = ConnectionHandler.create_owner_to_pool
937
962
 
938
963
  # Backup finalizer: if the forked child never needed a pool, the above
939
964
  # early discard has not occurred
@@ -62,6 +62,11 @@ module ActiveRecord
62
62
  def joins_per_query
63
63
  256
64
64
  end
65
+
66
+ private
67
+ def bind_params_length
68
+ 65535
69
+ end
65
70
  end
66
71
  end
67
72
  end
@@ -20,9 +20,22 @@ module ActiveRecord
20
20
  raise "Passing bind parameters with an arel AST is forbidden. " \
21
21
  "The values must be stored on the AST directly"
22
22
  end
23
- sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
24
- [sql.freeze, binds || []]
23
+
24
+ if prepared_statements
25
+ sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
26
+
27
+ if binds.length > bind_params_length
28
+ unprepared_statement do
29
+ sql, binds = to_sql_and_binds(arel_or_sql_string)
30
+ visitor.preparable = false
31
+ end
32
+ end
33
+ else
34
+ sql = visitor.accept(arel_or_sql_string.ast, collector).value
35
+ end
36
+ [sql.freeze, binds]
25
37
  else
38
+ visitor.preparable = false if prepared_statements
26
39
  [arel_or_sql_string.dup.freeze, binds]
27
40
  end
28
41
  end
@@ -46,11 +59,11 @@ module ActiveRecord
46
59
  def select_all(arel, name = nil, binds = [], preparable: nil)
47
60
  arel = arel_from_relation(arel)
48
61
  sql, binds = to_sql_and_binds(arel, binds)
49
- if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
50
- preparable = false
51
- else
52
- preparable = visitor.preparable
62
+
63
+ if preparable.nil?
64
+ preparable = prepared_statements ? visitor.preparable : false
53
65
  end
66
+
54
67
  if prepared_statements && preparable
55
68
  select_prepared(sql, name, binds)
56
69
  else