activerecord 5.2.0 → 5.2.1.rc1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/lib/active_record/associations.rb +9 -9
  4. data/lib/active_record/associations/alias_tracker.rb +1 -1
  5. data/lib/active_record/associations/association.rb +17 -10
  6. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  7. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  8. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  9. data/lib/active_record/associations/collection_association.rb +10 -7
  10. data/lib/active_record/associations/has_many_through_association.rb +1 -1
  11. data/lib/active_record/associations/has_one_association.rb +8 -0
  12. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  13. data/lib/active_record/associations/join_dependency.rb +39 -64
  14. data/lib/active_record/associations/join_dependency/join_association.rb +12 -18
  15. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  16. data/lib/active_record/associations/singular_association.rb +4 -10
  17. data/lib/active_record/associations/through_association.rb +1 -1
  18. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  19. data/lib/active_record/attribute_methods/read.rb +1 -1
  20. data/lib/active_record/autosave_association.rb +8 -3
  21. data/lib/active_record/callbacks.rb +4 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -12
  25. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +0 -11
  27. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  28. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  29. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +4 -0
  30. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  31. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +2 -2
  32. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -0
  33. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  34. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +0 -3
  35. data/lib/active_record/counter_cache.rb +17 -13
  36. data/lib/active_record/gem_version.rb +2 -2
  37. data/lib/active_record/log_subscriber.rb +1 -1
  38. data/lib/active_record/persistence.rb +1 -1
  39. data/lib/active_record/query_cache.rb +4 -11
  40. data/lib/active_record/relation.rb +13 -13
  41. data/lib/active_record/relation/finder_methods.rb +2 -4
  42. data/lib/active_record/relation/merger.rb +2 -6
  43. data/lib/active_record/relation/predicate_builder.rb +6 -5
  44. data/lib/active_record/relation/query_methods.rb +16 -13
  45. data/lib/active_record/timestamp.rb +8 -1
  46. data/lib/active_record/transactions.rb +23 -20
  47. data/lib/active_record/type/serialized.rb +4 -0
  48. metadata +11 -11
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  end
18
18
 
19
19
  def quoted_time(value)
20
+ value = value.change(year: 2000, month: 1, day: 1)
20
21
  quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
21
22
  end
22
23
 
@@ -7,6 +7,10 @@ module ActiveRecord
7
7
  # Returns an array of indexes for the given table.
8
8
  def indexes(table_name)
9
9
  exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
10
+ # Indexes SQLite creates implicitly for internal use start with "sqlite_".
11
+ # See https://www.sqlite.org/fileformat2.html#intschema
12
+ next if row["name"].starts_with?("sqlite_")
13
+
10
14
  index_sql = query_value(<<-SQL, "SCHEMA")
11
15
  SELECT sql
12
16
  FROM sqlite_master
@@ -40,7 +44,7 @@ module ActiveRecord
40
44
  where: where,
41
45
  orders: orders
42
46
  )
43
- end
47
+ end.compact
44
48
  end
45
49
 
46
50
  def create_schema_dumper(options)
@@ -453,9 +453,6 @@ module ActiveRecord
453
453
  def copy_table_indexes(from, to, rename = {})
454
454
  indexes(from).each do |index|
455
455
  name = index.name
456
- # indexes sqlite creates for internal use start with `sqlite_` and
457
- # don't need to be copied
458
- next if name.starts_with?("sqlite_")
459
456
  if to == "a#{from}"
460
457
  name = "t#{name}"
461
458
  elsif from == "a#{to}"
@@ -47,8 +47,12 @@ module ActiveRecord
47
47
  reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
48
48
  counter_name = reflection.counter_cache_column
49
49
 
50
- updates = { counter_name.to_sym => object.send(counter_association).count(:all) }
51
- updates.merge!(touch_updates(touch)) if touch
50
+ updates = { counter_name => object.send(counter_association).count(:all) }
51
+
52
+ if touch
53
+ names = touch if touch != true
54
+ updates.merge!(touch_attributes_with_time(*names))
55
+ end
52
56
 
53
57
  unscoped.where(primary_key => object.id).update_all(updates)
54
58
  end
@@ -68,8 +72,8 @@ module ActiveRecord
68
72
  # * +counters+ - A Hash containing the names of the fields
69
73
  # to update as keys and the amount to update the field by as values.
70
74
  # * <tt>:touch</tt> option - Touch timestamp columns when updating.
71
- # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
72
- # touch that column or an array of symbols to touch just those ones.
75
+ # If attribute names are passed, they are updated along with updated_at/on
76
+ # attributes.
73
77
  #
74
78
  # ==== Examples
75
79
  #
@@ -107,11 +111,18 @@ module ActiveRecord
107
111
  end
108
112
 
109
113
  if touch
110
- touch_updates = touch_updates(touch)
114
+ names = touch if touch != true
115
+ touch_updates = touch_attributes_with_time(*names)
111
116
  updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
112
117
  end
113
118
 
114
- unscoped.where(primary_key => id).update_all updates.join(", ")
119
+ if id.is_a?(Relation) && self == id.klass
120
+ relation = id
121
+ else
122
+ relation = unscoped.where!(primary_key => id)
123
+ end
124
+
125
+ relation.update_all updates.join(", ")
115
126
  end
116
127
 
117
128
  # Increment a numeric field by one, via a direct SQL update.
@@ -165,13 +176,6 @@ module ActiveRecord
165
176
  def decrement_counter(counter_name, id, touch: nil)
166
177
  update_counters(id, counter_name => -1, touch: touch)
167
178
  end
168
-
169
- private
170
- def touch_updates(touch)
171
- touch = timestamp_attributes_for_update_in_model if touch == true
172
- touch_time = current_time_from_proper_timezone
173
- Array(touch).map { |column| [ column, touch_time ] }.to_h
174
- end
175
179
  end
176
180
 
177
181
  private
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 5
11
11
  MINOR = 2
12
- TINY = 0
13
- PRE = nil
12
+ TINY = 1
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -125,7 +125,7 @@ module ActiveRecord
125
125
  ]
126
126
  end
127
127
 
128
- RAILS_GEM_ROOT = File.expand_path("../../../..", __FILE__) + "/"
128
+ RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
129
129
 
130
130
  def ignored_callstack(path)
131
131
  path.start_with?(RAILS_GEM_ROOT) ||
@@ -373,7 +373,7 @@ module ActiveRecord
373
373
  became = klass.allocate
374
374
  became.send(:initialize)
375
375
  became.instance_variable_set("@attributes", @attributes)
376
- became.instance_variable_set("@mutations_from_database", @mutations_from_database) if defined?(@mutations_from_database)
376
+ became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil)
377
377
  became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
378
378
  became.instance_variable_set("@new_record", new_record?)
379
379
  became.instance_variable_set("@destroyed", destroyed?)
@@ -26,19 +26,12 @@ module ActiveRecord
26
26
  end
27
27
 
28
28
  def self.run
29
- ActiveRecord::Base.connection_handler.connection_pool_list.map do |pool|
30
- caching_was_enabled = pool.query_cache_enabled
31
-
32
- pool.enable_query_cache!
33
-
34
- [pool, caching_was_enabled]
35
- end
29
+ ActiveRecord::Base.connection_handler.connection_pool_list.
30
+ reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }
36
31
  end
37
32
 
38
- def self.complete(caching_pools)
39
- caching_pools.each do |pool, caching_was_enabled|
40
- pool.disable_query_cache! unless caching_was_enabled
41
- end
33
+ def self.complete(pools)
34
+ pools.each { |pool| pool.disable_query_cache! }
42
35
 
43
36
  ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
44
37
  pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
@@ -277,10 +277,10 @@ module ActiveRecord
277
277
  # Please check unscoped if you want to remove all previous scopes (including
278
278
  # the default_scope) during the execution of a block.
279
279
  def scoping
280
- previous, klass.current_scope = klass.current_scope(true), self
280
+ previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
281
281
  yield
282
282
  ensure
283
- klass.current_scope = previous
283
+ klass.current_scope = previous unless @delegate_to_klass
284
284
  end
285
285
 
286
286
  def _exec_scope(*args, &block) # :nodoc:
@@ -436,17 +436,16 @@ module ActiveRecord
436
436
  # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
437
437
  def to_sql
438
438
  @to_sql ||= begin
439
- relation = self
440
-
441
- if eager_loading?
442
- apply_join_dependency { |rel, _| relation = rel }
443
- end
444
-
445
- conn = klass.connection
446
- conn.unprepared_statement {
447
- conn.to_sql(relation.arel)
448
- }
449
- end
439
+ if eager_loading?
440
+ apply_join_dependency do |relation, join_dependency|
441
+ relation = join_dependency.apply_column_aliases(relation)
442
+ relation.to_sql
443
+ end
444
+ else
445
+ conn = klass.connection
446
+ conn.unprepared_statement { conn.to_sql(arel) }
447
+ end
448
+ end
450
449
  end
451
450
 
452
451
  # Returns a hash of where conditions.
@@ -546,6 +545,7 @@ module ActiveRecord
546
545
  if ActiveRecord::NullRelation === relation
547
546
  []
548
547
  else
548
+ relation = join_dependency.apply_column_aliases(relation)
549
549
  rows = connection.select_all(relation.arel, "SQL")
550
550
  join_dependency.instantiate(rows, &block)
551
551
  end.freeze
@@ -373,13 +373,12 @@ module ActiveRecord
373
373
 
374
374
  def construct_join_dependency
375
375
  including = eager_load_values + includes_values
376
- joins = joins_values.select { |join| join.is_a?(Arel::Nodes::Join) }
377
376
  ActiveRecord::Associations::JoinDependency.new(
378
- klass, table, including, alias_tracker(joins)
377
+ klass, table, including
379
378
  )
380
379
  end
381
380
 
382
- def apply_join_dependency(eager_loading: true)
381
+ def apply_join_dependency(eager_loading: group_values.empty?)
383
382
  join_dependency = construct_join_dependency
384
383
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
385
384
 
@@ -392,7 +391,6 @@ module ActiveRecord
392
391
  end
393
392
 
394
393
  if block_given?
395
- relation._select!(join_dependency.aliases.columns)
396
394
  yield relation, join_dependency
397
395
  else
398
396
  relation
@@ -117,13 +117,11 @@ module ActiveRecord
117
117
  if other.klass == relation.klass
118
118
  relation.joins!(*other.joins_values)
119
119
  else
120
- alias_tracker = nil
121
120
  joins_dependency = other.joins_values.map do |join|
122
121
  case join
123
122
  when Hash, Symbol, Array
124
- alias_tracker ||= other.alias_tracker
125
123
  ActiveRecord::Associations::JoinDependency.new(
126
- other.klass, other.table, join, alias_tracker
124
+ other.klass, other.table, join
127
125
  )
128
126
  else
129
127
  join
@@ -140,13 +138,11 @@ module ActiveRecord
140
138
  if other.klass == relation.klass
141
139
  relation.left_outer_joins!(*other.left_outer_joins_values)
142
140
  else
143
- alias_tracker = nil
144
141
  joins_dependency = other.left_outer_joins_values.map do |join|
145
142
  case join
146
143
  when Hash, Symbol, Array
147
- alias_tracker ||= other.alias_tracker
148
144
  ActiveRecord::Associations::JoinDependency.new(
149
- other.klass, other.table, join, alias_tracker
145
+ other.klass, other.table, join
150
146
  )
151
147
  else
152
148
  join
@@ -48,7 +48,12 @@ module ActiveRecord
48
48
  end
49
49
 
50
50
  def build(attribute, value)
51
- handler_for(value).call(attribute, value)
51
+ if table.type(attribute.name).force_equality?(value)
52
+ bind = build_bind_attribute(attribute.name, value)
53
+ attribute.eq(bind)
54
+ else
55
+ handler_for(value).call(attribute, value)
56
+ end
52
57
  end
53
58
 
54
59
  def build_bind_attribute(column_name, value)
@@ -98,10 +103,6 @@ module ActiveRecord
98
103
  end.reduce(&:and)
99
104
  end
100
105
  queries.reduce(&:or)
101
- # FIXME: Deprecate this and provide a public API to force equality
102
- elsif (value.is_a?(Range) || value.is_a?(Array)) &&
103
- table.type(key.to_s).respond_to?(:subtype)
104
- BasicObjectHandler.new(self).call(table.arel_attribute(key), value)
105
106
  else
106
107
  build(table.arel_attribute(key), value)
107
108
  end
@@ -893,8 +893,8 @@ module ActiveRecord
893
893
  self
894
894
  end
895
895
 
896
- def skip_query_cache! # :nodoc:
897
- self.skip_query_cache_value = true
896
+ def skip_query_cache!(value = true) # :nodoc:
897
+ self.skip_query_cache_value = value
898
898
  self
899
899
  end
900
900
 
@@ -903,11 +903,12 @@ module ActiveRecord
903
903
  @arel ||= build_arel(aliases)
904
904
  end
905
905
 
906
+ # Returns a relation value with a given name
907
+ def get_value(name) # :nodoc:
908
+ @values.fetch(name, DEFAULT_VALUES[name])
909
+ end
910
+
906
911
  protected
907
- # Returns a relation value with a given name
908
- def get_value(name) # :nodoc:
909
- @values.fetch(name, DEFAULT_VALUES[name])
910
- end
911
912
 
912
913
  # Sets the relation value with the given name
913
914
  def set_value(name, value) # :nodoc:
@@ -1011,19 +1012,19 @@ module ActiveRecord
1011
1012
  def build_join_query(manager, buckets, join_type, aliases)
1012
1013
  buckets.default = []
1013
1014
 
1014
- association_joins = buckets[:association_join]
1015
- stashed_association_joins = buckets[:stashed_join]
1016
- join_nodes = buckets[:join_node].uniq
1017
- string_joins = buckets[:string_join].map(&:strip).uniq
1015
+ association_joins = buckets[:association_join]
1016
+ stashed_joins = buckets[:stashed_join]
1017
+ join_nodes = buckets[:join_node].uniq
1018
+ string_joins = buckets[:string_join].map(&:strip).uniq
1018
1019
 
1019
1020
  join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1020
1021
  alias_tracker = alias_tracker(join_list, aliases)
1021
1022
 
1022
1023
  join_dependency = ActiveRecord::Associations::JoinDependency.new(
1023
- klass, table, association_joins, alias_tracker
1024
+ klass, table, association_joins
1024
1025
  )
1025
1026
 
1026
- joins = join_dependency.join_constraints(stashed_association_joins, join_type)
1027
+ joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1027
1028
  joins.each { |join| manager.from(join) }
1028
1029
 
1029
1030
  manager.join_sources.concat(join_list)
@@ -1049,11 +1050,13 @@ module ActiveRecord
1049
1050
  end
1050
1051
 
1051
1052
  def arel_columns(columns)
1052
- columns.map do |field|
1053
+ columns.flat_map do |field|
1053
1054
  if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value
1054
1055
  arel_attribute(field)
1055
1056
  elsif Symbol === field
1056
1057
  connection.quote_table_name(field.to_s)
1058
+ elsif Proc === field
1059
+ field.call
1057
1060
  else
1058
1061
  field
1059
1062
  end
@@ -52,7 +52,14 @@ module ActiveRecord
52
52
  clear_timestamp_attributes
53
53
  end
54
54
 
55
- class_methods do
55
+ module ClassMethods # :nodoc:
56
+ def touch_attributes_with_time(*names, time: nil)
57
+ attribute_names = timestamp_attributes_for_update_in_model
58
+ attribute_names |= names.map(&:to_s)
59
+ time ||= current_time_from_proper_timezone
60
+ attribute_names.each_with_object({}) { |attr_name, result| result[attr_name] = time }
61
+ end
62
+
56
63
  private
57
64
  def timestamp_attributes_for_create_in_model
58
65
  timestamp_attributes_for_create.select { |c| column_names.include?(c) }
@@ -340,11 +340,13 @@ module ActiveRecord
340
340
  # Ensure that it is not called if the object was never persisted (failed create),
341
341
  # but call it after the commit of a destroyed object.
342
342
  def committed!(should_run_callbacks: true) #:nodoc:
343
- if should_run_callbacks && destroyed? || persisted?
343
+ if should_run_callbacks && (destroyed? || persisted?)
344
+ @_committed_already_called = true
344
345
  _run_commit_without_transaction_enrollment_callbacks
345
346
  _run_commit_callbacks
346
347
  end
347
348
  ensure
349
+ @_committed_already_called = false
348
350
  force_clear_transaction_record_state
349
351
  end
350
352
 
@@ -382,13 +384,7 @@ module ActiveRecord
382
384
  status = nil
383
385
  self.class.transaction do
384
386
  add_to_transaction
385
- begin
386
- status = yield
387
- rescue ActiveRecord::Rollback
388
- clear_transaction_record_state
389
- status = nil
390
- end
391
-
387
+ status = yield
392
388
  raise ActiveRecord::Rollback unless status
393
389
  end
394
390
  status
@@ -398,17 +394,29 @@ module ActiveRecord
398
394
  end
399
395
  end
400
396
 
397
+ protected
398
+ attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
399
+
401
400
  private
402
401
 
403
402
  # Save the new record state and id of a record so it can be restored later if a transaction fails.
404
403
  def remember_transaction_record_state
405
- @_start_transaction_state[:id] = id
406
404
  @_start_transaction_state.reverse_merge!(
405
+ id: id,
407
406
  new_record: @new_record,
408
407
  destroyed: @destroyed,
409
408
  frozen?: frozen?,
410
409
  )
411
410
  @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
411
+ remember_new_record_before_last_commit
412
+ end
413
+
414
+ def remember_new_record_before_last_commit
415
+ if _committed_already_called
416
+ @_new_record_before_last_commit = false
417
+ else
418
+ @_new_record_before_last_commit = @_start_transaction_state[:new_record]
419
+ end
412
420
  end
413
421
 
414
422
  # Clear the new record state and id of a record.
@@ -440,22 +448,16 @@ module ActiveRecord
440
448
  end
441
449
  end
442
450
 
443
- # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
444
- def transaction_record_state(state)
445
- @_start_transaction_state[state]
446
- end
447
-
448
451
  # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
449
452
  def transaction_include_any_action?(actions)
450
453
  actions.any? do |action|
451
454
  case action
452
455
  when :create
453
- transaction_record_state(:new_record)
454
- when :destroy
455
- defined?(@_trigger_destroy_callback) && @_trigger_destroy_callback
456
+ persisted? && @_new_record_before_last_commit
456
457
  when :update
457
- !(transaction_record_state(:new_record) || destroyed?) &&
458
- (defined?(@_trigger_update_callback) && @_trigger_update_callback)
458
+ !(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback
459
+ when :destroy
460
+ _trigger_destroy_callback
459
461
  end
460
462
  end
461
463
  end
@@ -491,7 +493,8 @@ module ActiveRecord
491
493
 
492
494
  def update_attributes_from_transaction_state(transaction_state)
493
495
  if transaction_state && transaction_state.finalized?
494
- restore_transaction_record_state if transaction_state.rolledback?
496
+ restore_transaction_record_state(transaction_state.fully_rolledback?) if transaction_state.rolledback?
497
+ force_clear_transaction_record_state if transaction_state.fully_committed?
495
498
  clear_transaction_record_state if transaction_state.fully_completed?
496
499
  end
497
500
  end