activerecord 5.1.4 → 5.1.5.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 (38) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +151 -0
  3. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  4. data/lib/active_record/associations/builder/belongs_to.rb +7 -3
  5. data/lib/active_record/associations/has_many_association.rb +1 -1
  6. data/lib/active_record/associations/join_dependency.rb +3 -3
  7. data/lib/active_record/associations/join_dependency/join_association.rb +1 -9
  8. data/lib/active_record/associations/preloader/association.rb +11 -34
  9. data/lib/active_record/associations/preloader/through_association.rb +10 -3
  10. data/lib/active_record/attribute_methods/dirty.rb +3 -2
  11. data/lib/active_record/collection_cache_key.rb +2 -2
  12. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  13. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -1
  14. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  15. data/lib/active_record/connection_adapters/abstract_adapter.rb +2 -1
  16. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +5 -4
  17. data/lib/active_record/connection_adapters/column.rb +1 -1
  18. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -0
  19. data/lib/active_record/connection_adapters/postgresql/column.rb +28 -1
  20. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  21. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +2 -1
  22. data/lib/active_record/connection_adapters/postgresql_adapter.rb +5 -3
  23. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +11 -4
  24. data/lib/active_record/gem_version.rb +2 -2
  25. data/lib/active_record/locking/pessimistic.rb +1 -1
  26. data/lib/active_record/log_subscriber.rb +3 -4
  27. data/lib/active_record/migration/compatibility.rb +34 -20
  28. data/lib/active_record/model_schema.rb +33 -33
  29. data/lib/active_record/persistence.rb +1 -5
  30. data/lib/active_record/query_cache.rb +6 -6
  31. data/lib/active_record/railties/databases.rake +2 -2
  32. data/lib/active_record/reflection.rb +24 -10
  33. data/lib/active_record/relation.rb +14 -2
  34. data/lib/active_record/relation/calculations.rb +16 -11
  35. data/lib/active_record/relation/finder_methods.rb +1 -11
  36. data/lib/active_record/relation/query_methods.rb +19 -14
  37. data/lib/active_record/tasks/database_tasks.rb +11 -10
  38. metadata +12 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 28e7916b1dbc3b40c76d1c05b4e30f04d00dc210
4
- data.tar.gz: 1ec9fb74675427203f1cded6cc322d39624cd3fd
2
+ SHA256:
3
+ metadata.gz: 6016e93d2aaf0a0b209c596bfd75f89345d4c5e33a254d901bca0c8363e67edc
4
+ data.tar.gz: 87a1c2845000e04e863351025880efcd8a3a9c432fac308f73370817cce8b3b7
5
5
  SHA512:
6
- metadata.gz: 94710c02a0c49929187fd1897b13d9a8cf8ea465c87c5ba90c0d17137d494ffeb08c64b38e278604e51670fa2bb74a36dce99721a1d16dee85728209902bfe18
7
- data.tar.gz: 78e89e05559a4ee6fb64d13937a83f87f004f1bf3e86b3917f3df8f3a7cf177f1f1d2d0164ca5f84d08944814324e35394b16b76da3d6c868ef2875ea9a12513
6
+ metadata.gz: 78de0a487a64f1f212ee5c89ac80806f4cc21dc44af76855c856aef70525508330f1a177745811a3d6eb658ad4c5be0c2ca66e6808a40cfcb15a2c39fab73791
7
+ data.tar.gz: faac29bf1b8260798c0a1416ea73b8c45dc0ec738122a8adf68598688fa1e84d94357d3bc6ab892bcebabd39c595a377bb928fab7556bcd9ec9784609aa87a9d
@@ -1,3 +1,154 @@
1
+ ## Rails 5.1.5.rc1 (February 01, 2018) ##
2
+
3
+ * Fix `count(:all)` with eager loading and having an order other than the driving table.
4
+
5
+ Fixes #31783.
6
+
7
+ *Ryuta Kamizono*
8
+
9
+ * Use `count(:all)` in `HasManyAssociation#count_records` to prevent invalid
10
+ SQL queries for association counting.
11
+
12
+ *Klas Eskilson*
13
+
14
+ * Fix to invoke callbacks when using `update_attribute`.
15
+
16
+ *Mike Busch*
17
+
18
+ * Fix `count(:all)` to correctly work `distinct` with custom SELECT list.
19
+
20
+ *Ryuta Kamizono*
21
+
22
+ * Fix conflicts `counter_cache` with `touch: true` by optimistic locking.
23
+
24
+ ```
25
+ # create_table :posts do |t|
26
+ # t.integer :comments_count, default: 0
27
+ # t.integer :lock_version
28
+ # t.timestamps
29
+ # end
30
+ class Post < ApplicationRecord
31
+ end
32
+
33
+ # create_table :comments do |t|
34
+ # t.belongs_to :post
35
+ # end
36
+ class Comment < ApplicationRecord
37
+ belongs_to :post, touch: true, counter_cache: true
38
+ end
39
+ ```
40
+
41
+ Before:
42
+ ```
43
+ post = Post.create!
44
+ # => begin transaction
45
+ INSERT INTO "posts" ("created_at", "updated_at", "lock_version")
46
+ VALUES ("2017-12-11 21:27:11.387397", "2017-12-11 21:27:11.387397", 0)
47
+ commit transaction
48
+
49
+ comment = Comment.create!(post: post)
50
+ # => begin transaction
51
+ INSERT INTO "comments" ("post_id") VALUES (1)
52
+
53
+ UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1,
54
+ "lock_version" = COALESCE("lock_version", 0) + 1 WHERE "posts"."id" = 1
55
+
56
+ UPDATE "posts" SET "updated_at" = '2017-12-11 21:27:11.398330',
57
+ "lock_version" = 1 WHERE "posts"."id" = 1 AND "posts"."lock_version" = 0
58
+ rollback transaction
59
+ # => ActiveRecord::StaleObjectError: Attempted to touch a stale object: Post.
60
+
61
+ Comment.take.destroy!
62
+ # => begin transaction
63
+ DELETE FROM "comments" WHERE "comments"."id" = 1
64
+
65
+ UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) - 1,
66
+ "lock_version" = COALESCE("lock_version", 0) + 1 WHERE "posts"."id" = 1
67
+
68
+ UPDATE "posts" SET "updated_at" = '2017-12-11 21:42:47.785901',
69
+ "lock_version" = 1 WHERE "posts"."id" = 1 AND "posts"."lock_version" = 0
70
+ rollback transaction
71
+ # => ActiveRecord::StaleObjectError: Attempted to touch a stale object: Post.
72
+ ```
73
+
74
+ After:
75
+ ```
76
+ post = Post.create!
77
+ # => begin transaction
78
+ INSERT INTO "posts" ("created_at", "updated_at", "lock_version")
79
+ VALUES ("2017-12-11 21:27:11.387397", "2017-12-11 21:27:11.387397", 0)
80
+ commit transaction
81
+
82
+ comment = Comment.create!(post: post)
83
+ # => begin transaction
84
+ INSERT INTO "comments" ("post_id") VALUES (1)
85
+
86
+ UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1,
87
+ "lock_version" = COALESCE("lock_version", 0) + 1,
88
+ "updated_at" = '2017-12-11 21:37:09.802642' WHERE "posts"."id" = 1
89
+ commit transaction
90
+
91
+ comment.destroy!
92
+ # => begin transaction
93
+ DELETE FROM "comments" WHERE "comments"."id" = 1
94
+
95
+ UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) - 1,
96
+ "lock_version" = COALESCE("lock_version", 0) + 1,
97
+ "updated_at" = '2017-12-11 21:39:02.685520' WHERE "posts"."id" = 1
98
+ commit transaction
99
+ ```
100
+
101
+ Fixes #31199.
102
+
103
+ *bogdanvlviv*
104
+
105
+ * Query cache was unavailable when entering the `ActiveRecord::Base.cache` block
106
+ without being connected.
107
+
108
+ *Tsukasa Oishi*
109
+
110
+ * Fix `bin/rails db:setup` and `bin/rails db:test:prepare` create wrong
111
+ ar_internal_metadata's data for a test database.
112
+
113
+ Before:
114
+ ```
115
+ $ RAILS_ENV=test rails dbconsole
116
+ > SELECT * FROM ar_internal_metadata;
117
+ key|value|created_at|updated_at
118
+ environment|development|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679
119
+ ```
120
+
121
+ After:
122
+ ```
123
+ $ RAILS_ENV=test rails dbconsole
124
+ > SELECT * FROM ar_internal_metadata;
125
+ key|value|created_at|updated_at
126
+ environment|test|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679
127
+ ```
128
+
129
+ Fixes #26731.
130
+
131
+ *bogdanvlviv*
132
+
133
+ * Fix longer sequence name detection for serial columns.
134
+
135
+ Fixes #28332.
136
+
137
+ *Ryuta Kamizono*
138
+
139
+ * MySQL: Don't lose `auto_increment: true` in the `db/schema.rb`.
140
+
141
+ Fixes #30894.
142
+
143
+ *Ryuta Kamizono*
144
+
145
+ * Fix `COUNT(DISTINCT ...)` for `GROUP BY` with `ORDER BY` and `LIMIT`.
146
+
147
+ Fixes #30886.
148
+
149
+ *Ryuta Kamizono*
150
+
151
+
1
152
  ## Rails 5.1.4 (September 07, 2017) ##
2
153
 
3
154
  * No changes.
@@ -47,9 +47,9 @@ module ActiveRecord
47
47
  def update_counters(by)
48
48
  if require_counter_update? && foreign_key_present?
49
49
  if target && !stale_target?
50
- target.increment!(reflection.counter_cache_column, by)
50
+ target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
51
51
  else
52
- klass.update_counters(target_id, reflection.counter_cache_column => by)
52
+ klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch])
53
53
  end
54
54
  end
55
55
  end
@@ -114,9 +114,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
114
114
  BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
115
115
  }}
116
116
 
117
- model.after_save callback.(:saved_changes), if: :saved_changes?
118
- model.after_touch callback.(:changes_to_save)
119
- model.after_destroy callback.(:changes_to_save)
117
+ unless reflection.counter_cache_column
118
+ model.after_create callback.(:saved_changes), if: :saved_changes?
119
+ model.after_destroy callback.(:changes_to_save)
120
+ end
121
+
122
+ model.after_update callback.(:saved_changes), if: :saved_changes?
123
+ model.after_touch callback.(:changes_to_save)
120
124
  end
121
125
 
122
126
  def self.add_default_callbacks(model, reflection)
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
  count = if reflection.has_cached_counter?
62
62
  owner._read_attribute(reflection.counter_cache_column).to_i
63
63
  else
64
- scope.count
64
+ scope.count(:all)
65
65
  end
66
66
 
67
67
  # If there's nothing in the database and @target has no new records
@@ -126,7 +126,7 @@ module ActiveRecord
126
126
  end
127
127
 
128
128
  def aliases
129
- Aliases.new join_root.each_with_index.map { |join_part, i|
129
+ @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
130
130
  columns = join_part.column_names.each_with_index.map { |column_name, j|
131
131
  Aliases::Column.new column_name, "t#{i}_r#{j}"
132
132
  }
@@ -134,7 +134,7 @@ module ActiveRecord
134
134
  }
135
135
  end
136
136
 
137
- def instantiate(result_set, aliases)
137
+ def instantiate(result_set, &block)
138
138
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
139
139
 
140
140
  seen = Hash.new { |i, object_id|
@@ -157,7 +157,7 @@ module ActiveRecord
157
157
  message_bus.instrument("instantiation.active_record", payload) do
158
158
  result_set.each { |row_hash|
159
159
  parent_key = primary_key ? row_hash[primary_key] : row_hash
160
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
160
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
161
161
  construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
162
162
  }
163
163
  end
@@ -40,15 +40,7 @@ module ActiveRecord
40
40
 
41
41
  constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
42
42
 
43
- predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
44
- scope_chain_items = reflection.join_scopes(table, predicate_builder)
45
- klass_scope = reflection.klass_join_scope(table, predicate_builder)
46
-
47
- scope_chain_items.concat [klass_scope].compact
48
-
49
- rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
50
- left.merge right
51
- end
43
+ rel = reflection.join_scope(table)
52
44
 
53
45
  if rel && !rel.arel.constraints.empty?
54
46
  binds += rel.bound_attributes
@@ -90,7 +90,11 @@ module ActiveRecord
90
90
  end
91
91
 
92
92
  def key_conversion_required?
93
- @key_conversion_required ||= association_key_type != owner_key_type
93
+ unless defined?(@key_conversion_required)
94
+ @key_conversion_required = (association_key_type != owner_key_type)
95
+ end
96
+
97
+ @key_conversion_required
94
98
  end
95
99
 
96
100
  def convert_key(key)
@@ -127,42 +131,15 @@ module ActiveRecord
127
131
  end
128
132
 
129
133
  def build_scope
130
- scope = klass.unscoped
131
-
132
- values = reflection_scope.values
133
- preload_values = preload_scope.values
134
-
135
- scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
136
- scope.references_values = Array(values[:references]) + Array(preload_values[:references])
137
-
138
- if preload_values[:select] || values[:select]
139
- scope._select!(preload_values[:select] || values[:select])
140
- end
141
- scope.includes! preload_values[:includes] || values[:includes]
142
- if preload_scope.joins_values.any?
143
- scope.joins!(preload_scope.joins_values)
144
- else
145
- scope.joins!(reflection_scope.joins_values)
146
- end
147
-
148
- if order_values = preload_values[:order] || values[:order]
149
- scope.order!(order_values)
150
- end
151
-
152
- if preload_values[:reordering] || values[:reordering]
153
- scope.reordering_value = true
154
- end
155
-
156
- if preload_values[:readonly] || values[:readonly]
157
- scope.readonly!
158
- end
134
+ scope = klass.scope_for_association
159
135
 
160
- if options[:as]
161
- scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
136
+ if reflection.type
137
+ scope.where!(reflection.type => model.base_class.sti_name)
162
138
  end
163
139
 
164
- scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
165
- klass.scope_for_association.merge(scope)
140
+ scope.merge!(reflection_scope)
141
+ scope.merge!(preload_scope) if preload_scope != NULL_RELATION
142
+ scope
166
143
  end
167
144
  end
168
145
  end
@@ -81,17 +81,24 @@ module ActiveRecord
81
81
 
82
82
  def through_scope
83
83
  scope = through_reflection.klass.unscoped
84
+ values = reflection_scope.values
84
85
 
85
86
  if options[:source_type]
86
87
  scope.where! reflection.foreign_type => options[:source_type]
87
88
  else
88
89
  unless reflection_scope.where_clause.empty?
89
- scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
90
+ scope.includes_values = Array(values[:includes] || options[:source])
90
91
  scope.where_clause = reflection_scope.where_clause
92
+ if joins = values[:joins]
93
+ scope.joins!(source_reflection.name => joins)
94
+ end
95
+ if left_outer_joins = values[:left_outer_joins]
96
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
97
+ end
91
98
  end
92
99
 
93
- scope.references! reflection_scope.values[:references]
94
- if scope.eager_loading? && order_values = reflection_scope.values[:order]
100
+ scope.references! values[:references]
101
+ if scope.eager_loading? && order_values = values[:order]
95
102
  scope = scope.order(order_values)
96
103
  end
97
104
  end
@@ -63,7 +63,7 @@ module ActiveRecord
63
63
  end
64
64
 
65
65
  def changes_internally_applied # :nodoc:
66
- @mutations_before_last_save = mutation_tracker
66
+ @mutations_before_last_save = mutations_from_database
67
67
  forget_attribute_assignments
68
68
  @mutations_from_database = AttributeMutationTracker.new(@attributes)
69
69
  end
@@ -71,7 +71,8 @@ module ActiveRecord
71
71
  def changes_applied # :nodoc:
72
72
  @previous_mutation_tracker = mutation_tracker
73
73
  @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
74
- clear_mutation_trackers
74
+ @mutation_tracker = nil
75
+ @mutations_from_database = nil
75
76
  end
76
77
 
77
78
  def clear_changes_information # :nodoc:
@@ -4,8 +4,8 @@ module ActiveRecord
4
4
  query_signature = Digest::MD5.hexdigest(collection.to_sql)
5
5
  key = "#{collection.model_name.cache_key}/query-#{query_signature}"
6
6
 
7
- if collection.loaded?
8
- size = collection.size
7
+ if collection.loaded? || collection.distinct_value
8
+ size = collection.records.size
9
9
  if size > 0
10
10
  timestamp = collection.max_by(&timestamp_column)._read_attribute(timestamp_column)
11
11
  end
@@ -108,6 +108,7 @@ module ActiveRecord
108
108
  "sql.active_record",
109
109
  sql: sql,
110
110
  binds: binds,
111
+ type_casted_binds: -> { type_casted_binds(binds) },
111
112
  name: name,
112
113
  connection_id: object_id,
113
114
  cached: true,
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  end
61
61
 
62
62
  def visit_PrimaryKeyDefinition(o)
63
- "PRIMARY KEY (#{o.name.join(', ')})"
63
+ "PRIMARY KEY (#{o.name.map { |name| quote_column_name(name) }.join(', ')})"
64
64
  end
65
65
 
66
66
  def visit_ForeignKeyDefinition(o)
@@ -121,7 +121,7 @@ module ActiveRecord
121
121
  end
122
122
 
123
123
  def polymorphic_options
124
- as_options(polymorphic).merge(null: options[:null])
124
+ as_options(polymorphic).merge(options.slice(:null, :first, :after))
125
125
  end
126
126
 
127
127
  def index_options
@@ -4,6 +4,7 @@ require "active_record/connection_adapters/schema_cache"
4
4
  require "active_record/connection_adapters/sql_type_metadata"
5
5
  require "active_record/connection_adapters/abstract/schema_dumper"
6
6
  require "active_record/connection_adapters/abstract/schema_creation"
7
+ require "active_support/concurrency/load_interlock_aware_monitor"
7
8
  require "arel/collectors/bind"
8
9
  require "arel/collectors/sql_string"
9
10
 
@@ -105,7 +106,7 @@ module ActiveRecord
105
106
  @schema_cache = SchemaCache.new self
106
107
  @quoted_column_names, @quoted_table_names = {}, {}
107
108
  @visitor = arel_visitor
108
- @lock = Monitor.new
109
+ @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
109
110
 
110
111
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
111
112
  @prepared_statements = true
@@ -140,11 +140,11 @@ module ActiveRecord
140
140
  end
141
141
 
142
142
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
143
- query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
143
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
144
144
  end
145
145
 
146
146
  def release_advisory_lock(lock_name) # :nodoc:
147
- query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
147
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
148
148
  end
149
149
 
150
150
  def native_database_types
@@ -464,12 +464,13 @@ module ActiveRecord
464
464
  fk.constraint_name AS 'name',
465
465
  rc.update_rule AS 'on_update',
466
466
  rc.delete_rule AS 'on_delete'
467
- FROM information_schema.key_column_usage fk
468
- JOIN information_schema.referential_constraints rc
467
+ FROM information_schema.referential_constraints rc
468
+ JOIN information_schema.key_column_usage fk
469
469
  USING (constraint_schema, constraint_name)
470
470
  WHERE fk.referenced_column_name IS NOT NULL
471
471
  AND fk.table_schema = #{scope[:schema]}
472
472
  AND fk.table_name = #{scope[:name]}
473
+ AND rc.constraint_schema = #{scope[:schema]}
473
474
  AND rc.table_name = #{scope[:name]}
474
475
  SQL
475
476