activerecord 7.2.2.2 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +316 -7
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/alias_tracker.rb +6 -4
  5. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  6. data/lib/active_record/associations/collection_association.rb +9 -7
  7. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  8. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  9. data/lib/active_record/attribute_methods.rb +24 -19
  10. data/lib/active_record/attributes.rb +37 -26
  11. data/lib/active_record/autosave_association.rb +22 -12
  12. data/lib/active_record/base.rb +2 -2
  13. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +49 -32
  14. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -6
  15. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -1
  16. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
  17. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -3
  18. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +14 -6
  19. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  20. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -3
  21. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
  22. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -12
  23. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  24. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
  25. data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -3
  26. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  27. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -0
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +10 -5
  29. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  30. data/lib/active_record/connection_handling.rb +12 -8
  31. data/lib/active_record/core.rb +27 -7
  32. data/lib/active_record/counter_cache.rb +1 -1
  33. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  34. data/lib/active_record/delegated_type.rb +18 -18
  35. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  36. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  37. data/lib/active_record/encryption/encryptor.rb +21 -20
  38. data/lib/active_record/enum.rb +13 -12
  39. data/lib/active_record/errors.rb +3 -3
  40. data/lib/active_record/fixture_set/table_row.rb +19 -2
  41. data/lib/active_record/gem_version.rb +2 -2
  42. data/lib/active_record/migration.rb +2 -1
  43. data/lib/active_record/query_logs.rb +4 -0
  44. data/lib/active_record/querying.rb +4 -4
  45. data/lib/active_record/railtie.rb +2 -2
  46. data/lib/active_record/railties/databases.rake +2 -1
  47. data/lib/active_record/relation/calculations.rb +35 -30
  48. data/lib/active_record/relation/finder_methods.rb +10 -10
  49. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  50. data/lib/active_record/relation/query_attribute.rb +1 -1
  51. data/lib/active_record/relation/query_methods.rb +16 -9
  52. data/lib/active_record/relation/where_clause.rb +8 -2
  53. data/lib/active_record/relation.rb +15 -5
  54. data/lib/active_record/schema_dumper.rb +29 -11
  55. data/lib/active_record/secure_token.rb +3 -3
  56. data/lib/active_record/signed_id.rb +7 -6
  57. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -0
  58. data/lib/active_record/transactions.rb +3 -1
  59. data/lib/active_record.rb +1 -1
  60. data/lib/arel/collectors/bind.rb +1 -1
  61. data/lib/arel/crud.rb +2 -0
  62. data/lib/arel/delete_manager.rb +5 -0
  63. data/lib/arel/nodes/delete_statement.rb +4 -2
  64. data/lib/arel/nodes/update_statement.rb +4 -2
  65. data/lib/arel/select_manager.rb +6 -2
  66. data/lib/arel/update_manager.rb +5 -0
  67. data/lib/arel/visitors/dot.rb +2 -0
  68. data/lib/arel/visitors/to_sql.rb +3 -1
  69. metadata +8 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9269c9c015fecf44abaa1366dde339c7c068c1a1f26f977060e50d2d91d9aed7
4
- data.tar.gz: 7718209bdff5867bb7df1bf94f975bac4a9e0807530c7bf8a51b1245a96229e1
3
+ metadata.gz: 0ebb786463cf3ba2ac8e8adc2945547e4e44a159d339035f4bbc4dc25cdd7bbc
4
+ data.tar.gz: d04ff22052095e10d2c4ab9f7bafdaedc550169faab2da5854095985acdb2f8f
5
5
  SHA512:
6
- metadata.gz: e7f135f22b059f74b1d6cbd64035d02f1c27b8d2cc767daf098165f41f0a76d283aeeec15db26589e303938ac1967f7c7a71a498d764b34b514020743c7eecea
7
- data.tar.gz: 8b2f0a0b826252e9bc5120dc3aa25e2aec88dc66e3bc09dfc71fd2baf6f6cfe25055db30d9554ceab80ef9cc035319e72b0fea1d83091b0a244d9d728c095174
6
+ metadata.gz: 9478c4f3eed1c66d601b1e0b8ba16c1585ccb7514b7cff14c2d1e84a7bd1255296243be1cb71e9f49ef1d8390c4e2ff7ad0bc26525dd2868bd1b46eb2922dbd7
7
+ data.tar.gz: 60aa9ebbf14c4ecb5afe54b02714a4802eafce2e99f84930170c1b28b639d762fd7a70d7d3afe4a1e9079b5f45bdc9c513ca6ac47927a4f1860ada9e6a25a0e4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,315 @@
1
+ ## Rails 7.2.3 (October 28, 2025) ##
2
+
3
+ * Fix SQLite3 data loss during table alterations with CASCADE foreign keys.
4
+
5
+ When altering a table in SQLite3 that is referenced by child tables with
6
+ `ON DELETE CASCADE` foreign keys, ActiveRecord would silently delete all
7
+ data from the child tables. This occurred because SQLite requires table
8
+ recreation for schema changes, and during this process the original table
9
+ is temporarily dropped, triggering CASCADE deletes on child tables.
10
+
11
+ The root cause was incorrect ordering of operations. The original code
12
+ wrapped `disable_referential_integrity` inside a transaction, but
13
+ `PRAGMA foreign_keys` cannot be modified inside a transaction in SQLite -
14
+ attempting to do so simply has no effect. This meant foreign keys remained
15
+ enabled during table recreation, causing CASCADE deletes to fire.
16
+
17
+ The fix reverses the order to follow the official SQLite 12-step ALTER TABLE
18
+ procedure: `disable_referential_integrity` now wraps the transaction instead
19
+ of being wrapped by it. This ensures foreign keys are properly disabled
20
+ before the transaction starts and re-enabled after it commits, preventing
21
+ CASCADE deletes while maintaining data integrity through atomic transactions.
22
+
23
+ *Ruy Rocha*
24
+
25
+ * Fix `belongs_to` associations not to clear the entire composite primary key.
26
+
27
+ When clearing a `belongs_to` association that references a model with composite primary key,
28
+ only the optional part of the key should be cleared.
29
+
30
+ *zzak*
31
+
32
+ * Fix invalid records being autosaved when distantly associated records are marked for deletion.
33
+
34
+ *Ian Terrell*, *axlekb AB*
35
+
36
+ * Prevent persisting invalid record.
37
+
38
+ *Edouard Chin*
39
+
40
+ * Fix count with group by qualified name on loaded relation.
41
+
42
+ *Ryuta Kamizono*
43
+
44
+ * Fix `sum` with qualified name on loaded relation.
45
+
46
+ *Chris Gunther*
47
+
48
+ * Fix prepared statements on mysql2 adapter.
49
+
50
+ *Jean Boussier*
51
+
52
+ * Fix query cache for pinned connections in multi threaded transactional tests.
53
+
54
+ When a pinned connection is used across separate threads, they now use a separate cache store
55
+ for each thread.
56
+
57
+ This improve accuracy of system tests, and any test using multiple threads.
58
+
59
+ *Heinrich Lee Yu*, *Jean Boussier*
60
+
61
+ * Don't add `id_value` attribute alias when attribute/column with that name already exists.
62
+
63
+ *Rob Lewis*
64
+
65
+ * Fix false positive change detection involving STI and polymorhic has one relationships.
66
+
67
+ Polymorphic `has_one` relationships would always be considered changed when defined in a STI child
68
+ class, causing nedless extra autosaves.
69
+
70
+ *David Fritsch*
71
+
72
+ * Fix stale associaton detection for polymophic `belong_to`.
73
+
74
+ *Florent Beaurain*, *Thomas Crambert*
75
+
76
+ * Fix removal of PostgreSQL version comments in `structure.sql` for latest PostgreSQL versions which include `\restrict`.
77
+
78
+ *Brendan Weibrecht*
79
+
80
+ * Fix `#merge` with `#or` or `#and` and a mixture of attributes and SQL strings resulting in an incorrect query.
81
+
82
+ ```ruby
83
+ base = Comment.joins(:post).where(user_id: 1).where("recent = 1")
84
+ puts base.merge(base.where(draft: true).or(Post.where(archived: true))).to_sql
85
+ ```
86
+
87
+ Before:
88
+
89
+ ```SQL
90
+ SELECT "comments".* FROM "comments"
91
+ INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
92
+ WHERE (recent = 1)
93
+ AND (
94
+ "comments"."user_id" = 1
95
+ AND (recent = 1)
96
+ AND "comments"."draft" = 1
97
+ OR "posts"."archived" = 1
98
+ )
99
+ ```
100
+
101
+ After:
102
+
103
+ ```SQL
104
+ SELECT "comments".* FROM "comments"
105
+ INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
106
+ WHERE "comments"."user_id" = 1
107
+ AND (recent = 1)
108
+ AND (
109
+ "comments"."user_id" = 1
110
+ AND (recent = 1)
111
+ AND "comments"."draft" = 1
112
+ OR "posts"."archived" = 1
113
+ )
114
+ ```
115
+
116
+ *Joshua Young*
117
+
118
+ * Fix inline `has_and_belongs_to_many` fixtures for tables with composite primary keys.
119
+
120
+ *fatkodima*
121
+
122
+ * Fix `annotate` comments to propagate to `update_all`/`delete_all`.
123
+
124
+ *fatkodima*
125
+
126
+ * Fix checking whether an unpersisted record is `include?`d in a strictly
127
+ loaded `has_and_belongs_to_many` association.
128
+
129
+ *Hartley McGuire*
130
+
131
+ * Fix inline has_and_belongs_to_many fixtures for tables with composite primary keys.
132
+
133
+ *fatkodima*
134
+
135
+ * `create_or_find_by` will now correctly rollback a transaction.
136
+
137
+ When using `create_or_find_by`, raising a ActiveRecord::Rollback error
138
+ in a `after_save` callback had no effect, the transaction was committed
139
+ and a record created.
140
+
141
+ *Edouard Chin*
142
+
143
+ * Gracefully handle `Timeout.timeout` firing during connection configuration.
144
+
145
+ Use of `Timeout.timeout` could result in improperly initialized database connection.
146
+
147
+ This could lead to a partially configured connection being used, resulting in various exceptions,
148
+ the most common being with the PostgreSQLAdapter raising `undefined method 'key?' for nil`
149
+ or `TypeError: wrong argument type nil (expected PG::TypeMap)`.
150
+
151
+ *Jean Boussier*
152
+
153
+ * The SQLite3 adapter quotes non-finite Numeric values like "Infinity" and "NaN".
154
+
155
+ *Mike Dalessio*
156
+
157
+ * Handle libpq returning a database version of 0 on no/bad connection in `PostgreSQLAdapter`.
158
+
159
+ Before, this version would be cached and an error would be raised during connection configuration when
160
+ comparing it with the minimum required version for the adapter. This meant that the connection could
161
+ never be successfully configured on subsequent reconnection attempts.
162
+
163
+ Now, this is treated as a connection failure consistent with libpq, raising a `ActiveRecord::ConnectionFailed`
164
+ and ensuring the version isn't cached, which allows the version to be retrieved on the next connection attempt.
165
+
166
+ *Joshua Young*, *Rian McGuire*
167
+
168
+ * Fix error handling during connection configuration.
169
+
170
+ Active Record wasn't properly handling errors during the connection configuration phase.
171
+ This could lead to a partially configured connection being used, resulting in various exceptions,
172
+ the most common being with the PostgreSQLAdapter raising `undefined method `key?' for nil`
173
+ or `TypeError: wrong argument type nil (expected PG::TypeMap)`.
174
+
175
+ *Jean Boussier*
176
+
177
+ * Fix a case where a non-retryable query could be marked retryable.
178
+
179
+ *Hartley McGuire*
180
+
181
+ * Handle circular references when autosaving associations.
182
+
183
+ *zzak*
184
+
185
+ * Prevent persisting invalid record.
186
+
187
+ *Edouard Chin*
188
+
189
+ * Fix support for PostgreSQL enum types with commas in their name.
190
+
191
+ *Arthur Hess*
192
+
193
+ * Fix inserts on MySQL with no RETURNING support for a table with multiple auto populated columns.
194
+
195
+ *Nikita Vasilevsky*
196
+
197
+ * Fix joining on a scoped association with string joins and bind parameters.
198
+
199
+ ```ruby
200
+ class Instructor < ActiveRecord::Base
201
+ has_many :instructor_roles, -> { active }
202
+ end
203
+
204
+ class InstructorRole < ActiveRecord::Base
205
+ scope :active, -> {
206
+ joins("JOIN students ON instructor_roles.student_id = students.id")
207
+ .where(students { status: 1 })
208
+ }
209
+ end
210
+
211
+ Instructor.joins(:instructor_roles).first
212
+ ```
213
+
214
+ The above example would result in `ActiveRecord::StatementInvalid` because the
215
+ `active` scope bind parameters would be lost.
216
+
217
+ *Jean Boussier*
218
+
219
+ * Fix a potential race condition with system tests and transactional fixtures.
220
+
221
+ *Sjoerd Lagarde*
222
+
223
+ * Fix count with group by qualified name on loaded relation.
224
+
225
+ *Ryuta Kamizono*
226
+
227
+ * Fix sum with qualified name on loaded relation.
228
+
229
+ *Chris Gunther*
230
+
231
+ * Fix autosave associations to no longer validated unmodified associated records.
232
+
233
+ Active Record was incorrectly performing validation on associated record that
234
+ weren't created nor modified as part of the transaction:
235
+
236
+ ```ruby
237
+ Post.create!(author: User.find(1)) # Fail if user is invalid
238
+ ```
239
+
240
+ *Jean Boussier*
241
+
242
+ * Remember when a database connection has recently been verified (for
243
+ two seconds, by default), to avoid repeated reverifications during a
244
+ single request.
245
+
246
+ This should recreate a similar rate of verification as in Rails 7.1,
247
+ where connections are leased for the duration of a request, and thus
248
+ only verified once.
249
+
250
+ *Matthew Draper*
251
+
252
+ * Fix prepared statements on mysql2 adapter.
253
+
254
+ *Jean Boussier*
255
+
256
+ * Fix a race condition in `ActiveRecord::Base#method_missing` when lazily defining attributes.
257
+
258
+ If multiple thread were concurrently triggering attribute definition on the same model,
259
+ it could result in a `NoMethodError` being raised.
260
+
261
+ *Jean Boussier*
262
+
263
+ * Fix MySQL default functions getting dropped when changing a column's nullability.
264
+
265
+ *Bastian Bartmann*
266
+
267
+ * Fix `add_unique_constraint`/`add_check_constraint`/`/`add_foreign_key` to be revertible when
268
+ given invalid options.
269
+
270
+ *fatkodima*
271
+
272
+ * Fix asynchronous destroying of polymorphic `belongs_to` associations.
273
+
274
+ *fatkodima*
275
+
276
+ * NOT VALID constraints should not dump in `create_table`.
277
+
278
+ *Ryuta Kamizono*
279
+
280
+ * Fix finding by nil composite primary key association.
281
+
282
+ *fatkodima*
283
+
284
+ * Fix parsing of SQLite foreign key names when they contain non-ASCII characters
285
+
286
+ *Zacharias Knudsen*
287
+
288
+ * Fix parsing of MySQL 8.0.16+ CHECK constraints when they contain new lines.
289
+
290
+ *Steve Hill*
291
+
292
+ * Ensure normalized attribute queries use `IS NULL` consistently for `nil` and normalized `nil` values.
293
+
294
+ *Joshua Young*
295
+
296
+ * Restore back the ability to pass only database name for `DATABASE_URL`.
297
+
298
+ *fatkodima*
299
+
300
+ * Fix `order` with using association name as an alias.
301
+
302
+ *Ryuta Kamizono*
303
+
304
+ * Improve invalid argument error for with.
305
+
306
+ *Ryuta Kamizono*
307
+
308
+ * Deduplicate `with` CTE expressions.
309
+
310
+ *fatkodima*
311
+
312
+
1
313
  ## Rails 7.2.2.2 (August 13, 2025) ##
2
314
 
3
315
  * Call inspect on ids in RecordNotFound error
@@ -6,6 +318,7 @@
6
318
 
7
319
  *Gannon McGibbon*, *John Hawthorn*
8
320
 
321
+
9
322
  ## Rails 7.2.2.1 (December 10, 2024) ##
10
323
 
11
324
  * No changes.
@@ -736,20 +1049,16 @@
736
1049
 
737
1050
  *Jason Nochlin*
738
1051
 
739
- * The fix ensures that the association is joined using the appropriate join type
740
- (either inner join or left outer join) based on the existing joins in the scope.
741
-
742
- This prevents unintentional overrides of existing join types and ensures consistency in the generated SQL queries.
1052
+ * Fix an issue with `where.associated` losing the current join type scope.
743
1053
 
744
1054
  Example:
745
1055
 
746
-
747
-
748
1056
  ```ruby
749
- # `associated` will use `LEFT JOIN` instead of using `JOIN`
750
1057
  Post.left_joins(:author).where.associated(:author)
751
1058
  ```
752
1059
 
1060
+ Previously, the `LEFT OUTER JOIN` would be lost and converted to an `INNER JOIN`.
1061
+
753
1062
  *Saleh Alhaddad*
754
1063
 
755
1064
  * Fix an issue where `ActiveRecord::Encryption` configurations are not ready before the loading
data/README.rdoc CHANGED
@@ -214,6 +214,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
214
214
 
215
215
  * https://github.com/rails/rails/issues
216
216
 
217
- Feature requests should be discussed on the rails-core mailing list here:
217
+ Feature requests should be discussed on the rubyonrails-core forum here:
218
218
 
219
219
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -26,16 +26,18 @@ module ActiveRecord
26
26
  end
27
27
 
28
28
  def self.initial_count_for(connection, name, table_joins)
29
- quoted_name = nil
29
+ quoted_name_escaped = nil
30
+ name_escaped = nil
30
31
 
31
32
  counts = table_joins.map do |join|
32
33
  if join.is_a?(Arel::Nodes::StringJoin)
33
- # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase
34
- quoted_name ||= connection.quote_table_name(name)
34
+ # quoted_name_escaped should be case ignored as some database adapters (Oracle) return quoted name in uppercase
35
+ quoted_name_escaped ||= Regexp.escape(connection.quote_table_name(name))
36
+ name_escaped ||= Regexp.escape(name)
35
37
 
36
38
  # Table names + table aliases
37
39
  join.left.scan(
38
- /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
40
+ /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name_escaped}|#{name_escaped})\sON/i
39
41
  ).size
40
42
  elsif join.is_a?(Arel::Nodes::Join)
41
43
  join.left.name == name ? 1 : 0
@@ -19,10 +19,16 @@ module ActiveRecord
19
19
  id = owner.public_send(reflection.foreign_key)
20
20
  end
21
21
 
22
+ association_class = if reflection.polymorphic?
23
+ owner.public_send(reflection.foreign_type)
24
+ else
25
+ reflection.klass
26
+ end
27
+
22
28
  enqueue_destroy_association(
23
29
  owner_model_name: owner.class.to_s,
24
30
  owner_id: owner.id,
25
- association_class: reflection.klass.to_s,
31
+ association_class: association_class.to_s,
26
32
  association_ids: [id],
27
33
  association_primary_key_column: primary_key_column,
28
34
  ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
@@ -129,7 +135,9 @@ module ActiveRecord
129
135
  target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
130
136
 
131
137
  if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
138
+ owner_pk = Array(owner.class.primary_key)
132
139
  reflection_fk.each_with_index do |key, index|
140
+ next if record.nil? && owner_pk.include?(key)
133
141
  owner[key] = target_key_values[index]
134
142
  end
135
143
  end
@@ -156,7 +164,15 @@ module ActiveRecord
156
164
  end
157
165
 
158
166
  def stale_state
159
- owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
167
+ foreign_key = reflection.foreign_key
168
+ if foreign_key.is_a?(Array)
169
+ attributes = foreign_key.map do |fk|
170
+ owner._read_attribute(fk) { |n| owner.send(:missing_attribute, n, caller) }
171
+ end
172
+ attributes if attributes.any?
173
+ else
174
+ owner._read_attribute(foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
175
+ end
160
176
  end
161
177
  end
162
178
  end
@@ -256,14 +256,16 @@ module ActiveRecord
256
256
  end
257
257
 
258
258
  def include?(record)
259
- if record.is_a?(reflection.klass)
260
- if record.new_record?
261
- include_in_memory?(record)
262
- else
263
- loaded? ? target.include?(record) : scope.exists?(record.id)
264
- end
259
+ klass = reflection.klass
260
+ return false unless record.is_a?(klass)
261
+
262
+ if loaded?
263
+ target.include?(record)
264
+ elsif record.new_record?
265
+ include_in_memory?(record)
265
266
  else
266
- false
267
+ record_id = klass.composite_primary_key? ? klass.primary_key.zip(record.id).to_h : record.id
268
+ scope.exists?(record_id)
267
269
  end
268
270
  end
269
271
 
@@ -38,41 +38,39 @@ module ActiveRecord
38
38
  chain << [reflection, table]
39
39
  end
40
40
 
41
- base_klass.with_connection do |connection|
42
- # The chain starts with the target table, but we want to end with it here (makes
43
- # more sense in this context), so we reverse
44
- chain.reverse_each do |reflection, table|
45
- klass = reflection.klass
41
+ # The chain starts with the target table, but we want to end with it here (makes
42
+ # more sense in this context), so we reverse
43
+ chain.reverse_each do |reflection, table|
44
+ klass = reflection.klass
46
45
 
47
- scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
48
47
 
49
- unless scope.references_values.empty?
50
- associations = scope.eager_load_values | scope.includes_values
48
+ unless scope.references_values.empty?
49
+ associations = scope.eager_load_values | scope.includes_values
51
50
 
52
- unless associations.empty?
53
- scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
54
- end
51
+ unless associations.empty?
52
+ scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
55
53
  end
54
+ end
56
55
 
57
- arel = scope.arel(alias_tracker.aliases)
58
- nodes = arel.constraints.first
56
+ arel = scope.arel(alias_tracker.aliases)
57
+ nodes = arel.constraints.first
59
58
 
60
- if nodes.is_a?(Arel::Nodes::And)
61
- others = nodes.children.extract! do |node|
62
- !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
63
- end
59
+ if nodes.is_a?(Arel::Nodes::And)
60
+ others = nodes.children.extract! do |node|
61
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
64
62
  end
63
+ end
65
64
 
66
- joins << join_type.new(table, Arel::Nodes::On.new(nodes))
65
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
67
66
 
68
- if others && !others.empty?
69
- joins.concat arel.join_sources
70
- append_constraints(connection, joins.last, others)
71
- end
72
-
73
- # The current table in this iteration becomes the foreign table in the next
74
- foreign_table, foreign_klass = table, klass
67
+ if others && !others.empty?
68
+ joins.concat arel.join_sources
69
+ append_constraints(joins.last, others)
75
70
  end
71
+
72
+ # The current table in this iteration becomes the foreign table in the next
73
+ foreign_table, foreign_klass = table, klass
76
74
  end
77
75
 
78
76
  joins
@@ -91,10 +89,10 @@ module ActiveRecord
91
89
  end
92
90
 
93
91
  private
94
- def append_constraints(connection, join, constraints)
92
+ def append_constraints(join, constraints)
95
93
  if join.is_a?(Arel::Nodes::StringJoin)
96
94
  join_string = Arel::Nodes::And.new(constraints.unshift join.left)
97
- join.left = Arel.sql(connection.visitor.compile(join_string))
95
+ join.left = join_string
98
96
  else
99
97
  right = join.right
100
98
  right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
@@ -130,7 +130,7 @@ module ActiveRecord
130
130
  # silently cast unsupported types to +String+:
131
131
  #
132
132
  # >> JSON.parse(JSON.dump(Struct.new(:foo)))
133
- # => "#<Class:0x000000013090b4c0>"
133
+ # # => "#<Class:0x000000013090b4c0>"
134
134
  #
135
135
  # ==== Examples
136
136
  #
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  attribute_method_patterns_cache.clear
85
85
  end
86
86
 
87
- def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
87
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
88
88
  old_name = old_name.to_s
89
89
 
90
90
  if !abstract_class? && !has_attribute?(old_name)
@@ -113,13 +113,14 @@ module ActiveRecord
113
113
  unless abstract_class?
114
114
  load_schema
115
115
  super(attribute_names)
116
- alias_attribute :id_value, :id if _has_attribute?("id")
116
+ alias_attribute :id_value, :id if _has_attribute?("id") && !_has_attribute?("id_value")
117
117
  end
118
118
 
119
- @attribute_methods_generated = true
120
-
121
119
  generate_alias_attributes
120
+
121
+ @attribute_methods_generated = true
122
122
  end
123
+
123
124
  true
124
125
  end
125
126
 
@@ -472,23 +473,27 @@ module ActiveRecord
472
473
  end
473
474
 
474
475
  def method_missing(name, ...)
475
- unless self.class.attribute_methods_generated?
476
- if self.class.method_defined?(name)
477
- # The method is explicitly defined in the model, but calls a generated
478
- # method with super. So we must resume the call chain at the right step.
479
- last_method = method(name)
480
- last_method = last_method.super_method while last_method.super_method
481
- self.class.define_attribute_methods
482
- if last_method.super_method
483
- return last_method.super_method.call(...)
484
- end
485
- elsif self.class.define_attribute_methods
486
- # Some attribute methods weren't generated yet, we retry the call
487
- return public_send(name, ...)
488
- end
476
+ # We can't know whether some method was defined or not because
477
+ # multiple thread might be concurrently be in this code path.
478
+ # So the first one would define the methods and the others would
479
+ # appear to already have them.
480
+ self.class.define_attribute_methods
481
+
482
+ # So in all cases we must behave as if the method was just defined.
483
+ method = begin
484
+ self.class.public_instance_method(name)
485
+ rescue NameError
486
+ nil
489
487
  end
490
488
 
491
- super
489
+ # The method might be explicitly defined in the model, but call a generated
490
+ # method with super. So we must resume the call chain at the right step.
491
+ method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods)
492
+ if method
493
+ method.bind_call(self, ...)
494
+ else
495
+ super
496
+ end
492
497
  end
493
498
 
494
499
  def attribute_method?(attr_name)