activerecord 6.0.0.beta1 → 6.0.0

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +455 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record/associations/association.rb +18 -1
  5. data/lib/active_record/associations/builder/association.rb +14 -18
  6. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  7. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  8. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  9. data/lib/active_record/associations/builder/has_many.rb +2 -0
  10. data/lib/active_record/associations/builder/has_one.rb +35 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  12. data/lib/active_record/associations/collection_association.rb +5 -6
  13. data/lib/active_record/associations/collection_proxy.rb +13 -42
  14. data/lib/active_record/associations/has_many_association.rb +1 -9
  15. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  16. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  17. data/lib/active_record/associations/join_dependency.rb +10 -9
  18. data/lib/active_record/associations/preloader/association.rb +37 -34
  19. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  20. data/lib/active_record/associations/preloader.rb +11 -6
  21. data/lib/active_record/associations.rb +3 -2
  22. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  23. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  24. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  25. data/lib/active_record/attribute_methods/query.rb +2 -3
  26. data/lib/active_record/attribute_methods/read.rb +3 -9
  27. data/lib/active_record/attribute_methods/write.rb +6 -12
  28. data/lib/active_record/attribute_methods.rb +3 -53
  29. data/lib/active_record/attributes.rb +13 -0
  30. data/lib/active_record/autosave_association.rb +15 -5
  31. data/lib/active_record/base.rb +0 -1
  32. data/lib/active_record/callbacks.rb +3 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
  45. data/lib/active_record/connection_adapters/column.rb +17 -13
  46. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  47. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  48. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  49. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  51. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  52. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  53. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  58. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  59. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  61. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  64. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  65. data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
  66. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  67. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  68. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  69. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
  72. data/lib/active_record/connection_handling.rb +32 -16
  73. data/lib/active_record/core.rb +27 -20
  74. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  75. data/lib/active_record/database_configurations/url_config.rb +21 -16
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/dynamic_matchers.rb +1 -1
  78. data/lib/active_record/enum.rb +15 -0
  79. data/lib/active_record/errors.rb +18 -13
  80. data/lib/active_record/fixtures.rb +11 -6
  81. data/lib/active_record/gem_version.rb +1 -1
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +179 -0
  84. data/lib/active_record/integration.rb +13 -1
  85. data/lib/active_record/internal_metadata.rb +5 -1
  86. data/lib/active_record/locking/optimistic.rb +3 -4
  87. data/lib/active_record/log_subscriber.rb +1 -1
  88. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  89. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/migration/command_recorder.rb +28 -14
  92. data/lib/active_record/migration/compatibility.rb +72 -63
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/persistence.rb +212 -19
  95. data/lib/active_record/querying.rb +18 -14
  96. data/lib/active_record/railtie.rb +9 -1
  97. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  98. data/lib/active_record/railties/databases.rake +124 -25
  99. data/lib/active_record/reflection.rb +18 -32
  100. data/lib/active_record/relation/calculations.rb +40 -44
  101. data/lib/active_record/relation/delegation.rb +23 -31
  102. data/lib/active_record/relation/finder_methods.rb +13 -13
  103. data/lib/active_record/relation/merger.rb +11 -16
  104. data/lib/active_record/relation/query_attribute.rb +5 -3
  105. data/lib/active_record/relation/query_methods.rb +217 -68
  106. data/lib/active_record/relation/spawn_methods.rb +1 -1
  107. data/lib/active_record/relation/where_clause.rb +10 -10
  108. data/lib/active_record/relation.rb +184 -35
  109. data/lib/active_record/sanitization.rb +33 -4
  110. data/lib/active_record/schema.rb +1 -1
  111. data/lib/active_record/schema_dumper.rb +10 -1
  112. data/lib/active_record/schema_migration.rb +1 -1
  113. data/lib/active_record/scoping/default.rb +7 -15
  114. data/lib/active_record/scoping/named.rb +10 -2
  115. data/lib/active_record/scoping.rb +6 -7
  116. data/lib/active_record/statement_cache.rb +2 -2
  117. data/lib/active_record/store.rb +48 -0
  118. data/lib/active_record/table_metadata.rb +9 -13
  119. data/lib/active_record/tasks/database_tasks.rb +109 -6
  120. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  121. data/lib/active_record/test_databases.rb +1 -16
  122. data/lib/active_record/test_fixtures.rb +2 -2
  123. data/lib/active_record/timestamp.rb +35 -19
  124. data/lib/active_record/touch_later.rb +4 -2
  125. data/lib/active_record/transactions.rb +55 -45
  126. data/lib/active_record/type_caster/connection.rb +16 -10
  127. data/lib/active_record/validations/uniqueness.rb +4 -4
  128. data/lib/active_record/validations.rb +1 -0
  129. data/lib/active_record.rb +7 -1
  130. data/lib/arel/insert_manager.rb +3 -3
  131. data/lib/arel/nodes/and.rb +1 -1
  132. data/lib/arel/nodes/case.rb +1 -1
  133. data/lib/arel/nodes/comment.rb +29 -0
  134. data/lib/arel/nodes/select_core.rb +16 -12
  135. data/lib/arel/nodes/unary.rb +1 -0
  136. data/lib/arel/nodes/values_list.rb +2 -17
  137. data/lib/arel/nodes.rb +2 -1
  138. data/lib/arel/select_manager.rb +10 -10
  139. data/lib/arel/visitors/depth_first.rb +7 -2
  140. data/lib/arel/visitors/dot.rb +7 -2
  141. data/lib/arel/visitors/ibm_db.rb +13 -0
  142. data/lib/arel/visitors/informix.rb +6 -0
  143. data/lib/arel/visitors/mssql.rb +15 -1
  144. data/lib/arel/visitors/oracle12.rb +4 -5
  145. data/lib/arel/visitors/postgresql.rb +4 -10
  146. data/lib/arel/visitors/to_sql.rb +107 -131
  147. data/lib/arel/visitors/visitor.rb +9 -5
  148. data/lib/arel.rb +7 -0
  149. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  150. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  151. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  152. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  153. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  154. metadata +17 -13
  155. data/lib/active_record/collection_cache_key.rb +0 -53
  156. data/lib/arel/nodes/values.rb +0 -16
@@ -4,17 +4,18 @@ module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
5
  delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true
6
6
 
7
- def initialize(klass, arel_table, association = nil)
7
+ def initialize(klass, arel_table, association = nil, types = klass)
8
8
  @klass = klass
9
+ @types = types
9
10
  @arel_table = arel_table
10
11
  @association = association
11
12
  end
12
13
 
13
14
  def resolve_column_aliases(hash)
14
15
  new_hash = hash.dup
15
- hash.each do |key, _|
16
- if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
17
- new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
16
+ hash.each_key do |key|
17
+ if key.is_a?(Symbol) && new_key = klass.attribute_aliases[key.to_s]
18
+ new_hash[new_key] = new_hash.delete(key)
18
19
  end
19
20
  end
20
21
  new_hash
@@ -29,11 +30,7 @@ module ActiveRecord
29
30
  end
30
31
 
31
32
  def type(column_name)
32
- if klass
33
- klass.type_for_attribute(column_name)
34
- else
35
- Type.default_value
36
- end
33
+ types.type_for_attribute(column_name)
37
34
  end
38
35
 
39
36
  def has_column?(column_name)
@@ -52,13 +49,12 @@ module ActiveRecord
52
49
  elsif association && !association.polymorphic?
53
50
  association_klass = association.klass
54
51
  arel_table = association_klass.arel_table.alias(table_name)
52
+ TableMetadata.new(association_klass, arel_table, association)
55
53
  else
56
54
  type_caster = TypeCaster::Connection.new(klass, table_name)
57
- association_klass = nil
58
55
  arel_table = Arel::Table.new(table_name, type_caster: type_caster)
56
+ TableMetadata.new(nil, arel_table, association, type_caster)
59
57
  end
60
-
61
- TableMetadata.new(association_klass, arel_table, association)
62
58
  end
63
59
 
64
60
  def polymorphic_association?
@@ -74,6 +70,6 @@ module ActiveRecord
74
70
  end
75
71
 
76
72
  private
77
- attr_reader :klass, :arel_table, :association
73
+ attr_reader :klass, :types, :arel_table, :association
78
74
  end
79
75
  end
@@ -141,8 +141,21 @@ module ActiveRecord
141
141
  end
142
142
  end
143
143
 
144
- def for_each
145
- databases = Rails.application.config.database_configuration
144
+ def setup_initial_database_yaml
145
+ return {} unless defined?(Rails)
146
+
147
+ begin
148
+ Rails.application.config.load_database_yaml
149
+ rescue
150
+ $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
151
+
152
+ {}
153
+ end
154
+ end
155
+
156
+ def for_each(databases)
157
+ return {} unless defined?(Rails)
158
+
146
159
  database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
147
160
 
148
161
  # if this is a single database application we don't want tasks for each primary database
@@ -153,8 +166,22 @@ module ActiveRecord
153
166
  end
154
167
  end
155
168
 
156
- def create_current(environment = env)
157
- each_current_configuration(environment) { |configuration|
169
+ def raise_for_multi_db(environment = env, command:)
170
+ db_configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
171
+
172
+ if db_configs.count > 1
173
+ dbs_list = []
174
+
175
+ db_configs.each do |db|
176
+ dbs_list << "#{command}:#{db.spec_name}"
177
+ end
178
+
179
+ raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}."
180
+ end
181
+ end
182
+
183
+ def create_current(environment = env, spec_name = nil)
184
+ each_current_configuration(environment, spec_name) { |configuration|
158
185
  create configuration
159
186
  }
160
187
  ActiveRecord::Base.establish_connection(environment.to_sym)
@@ -182,6 +209,26 @@ module ActiveRecord
182
209
  }
183
210
  end
184
211
 
212
+ def truncate_tables(configuration)
213
+ ActiveRecord::Base.connected_to(database: { truncation: configuration }) do
214
+ conn = ActiveRecord::Base.connection
215
+ table_names = conn.tables
216
+ table_names -= [
217
+ conn.schema_migration.table_name,
218
+ InternalMetadata.table_name
219
+ ]
220
+
221
+ ActiveRecord::Base.connection.truncate_tables(*table_names)
222
+ end
223
+ end
224
+ private :truncate_tables
225
+
226
+ def truncate_all(environment = env)
227
+ ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config|
228
+ truncate_tables db_config.config
229
+ end
230
+ end
231
+
185
232
  def migrate
186
233
  check_target_version
187
234
 
@@ -198,7 +245,7 @@ module ActiveRecord
198
245
  end
199
246
 
200
247
  def migrate_status
201
- unless ActiveRecord::SchemaMigration.table_exists?
248
+ unless ActiveRecord::Base.connection.schema_migration.table_exists?
202
249
  Kernel.abort "Schema migrations table does not exist yet."
203
250
  end
204
251
 
@@ -286,10 +333,60 @@ module ActiveRecord
286
333
  end
287
334
  ActiveRecord::InternalMetadata.create_table
288
335
  ActiveRecord::InternalMetadata[:environment] = environment
336
+ ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file)
289
337
  ensure
290
338
  Migration.verbose = verbose_was
291
339
  end
292
340
 
341
+ def schema_up_to_date?(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env, spec_name = "primary")
342
+ file ||= dump_filename(spec_name, format)
343
+
344
+ return true unless File.exist?(file)
345
+
346
+ ActiveRecord::Base.establish_connection(configuration)
347
+ return false unless ActiveRecord::InternalMetadata.table_exists?
348
+ ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file)
349
+ end
350
+
351
+ def reconstruct_from_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env, spec_name = "primary") # :nodoc:
352
+ file ||= dump_filename(spec_name, format)
353
+
354
+ check_schema_file(file)
355
+
356
+ ActiveRecord::Base.establish_connection(configuration)
357
+
358
+ if schema_up_to_date?(configuration, format, file, environment, spec_name)
359
+ truncate_tables(configuration)
360
+ else
361
+ purge(configuration)
362
+ load_schema(configuration, format, file, environment, spec_name)
363
+ end
364
+ rescue ActiveRecord::NoDatabaseError
365
+ create(configuration)
366
+ load_schema(configuration, format, file, environment, spec_name)
367
+ end
368
+
369
+ def dump_schema(configuration, format = ActiveRecord::Base.schema_format, spec_name = "primary") # :nodoc:
370
+ require "active_record/schema_dumper"
371
+ filename = dump_filename(spec_name, format)
372
+ connection = ActiveRecord::Base.connection
373
+
374
+ case format
375
+ when :ruby
376
+ File.open(filename, "w:utf-8") do |file|
377
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
378
+ end
379
+ when :sql
380
+ structure_dump(configuration, filename)
381
+ if connection.schema_migration.table_exists?
382
+ File.open(filename, "a") do |f|
383
+ f.puts connection.dump_schema_information
384
+ f.print "\n"
385
+ end
386
+ end
387
+ end
388
+ end
389
+
293
390
  def schema_file(format = ActiveRecord::Base.schema_format)
294
391
  File.join(db_dir, schema_file_type(format))
295
392
  end
@@ -371,12 +468,14 @@ module ActiveRecord
371
468
  task.is_a?(String) ? task.constantize : task
372
469
  end
373
470
 
374
- def each_current_configuration(environment)
471
+ def each_current_configuration(environment, spec_name = nil)
375
472
  environments = [environment]
376
473
  environments << "test" if environment == "development"
377
474
 
378
475
  environments.each do |env|
379
476
  ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
477
+ next if spec_name && spec_name != db_config.spec_name
478
+
380
479
  yield db_config.config, db_config.spec_name, env
381
480
  end
382
481
  end
@@ -398,6 +497,10 @@ module ActiveRecord
398
497
  def local_database?(configuration)
399
498
  configuration["host"].blank? || LOCAL_HOSTS.include?(configuration["host"])
400
499
  end
500
+
501
+ def schema_sha1(file)
502
+ Digest::SHA1.hexdigest(File.read(file))
503
+ end
401
504
  end
402
505
  end
403
506
  end
@@ -3,6 +3,8 @@
3
3
  module ActiveRecord
4
4
  module Tasks # :nodoc:
5
5
  class MySQLDatabaseTasks # :nodoc:
6
+ ER_DB_CREATE_EXISTS = 1007
7
+
6
8
  delegate :connection, :establish_connection, to: ActiveRecord::Base
7
9
 
8
10
  def initialize(configuration)
@@ -14,7 +16,7 @@ module ActiveRecord
14
16
  connection.create_database configuration["database"], creation_options
15
17
  establish_connection configuration
16
18
  rescue ActiveRecord::StatementInvalid => error
17
- if error.message.include?("database exists")
19
+ if connection.error_number(error.cause) == ER_DB_CREATE_EXISTS
18
20
  raise DatabaseAlreadyExists
19
21
  else
20
22
  raise
@@ -8,31 +8,16 @@ module ActiveRecord
8
8
  create_and_load_schema(i, env_name: Rails.env)
9
9
  end
10
10
 
11
- ActiveSupport::Testing::Parallelization.run_cleanup_hook do
12
- drop(env_name: Rails.env)
13
- end
14
-
15
11
  def self.create_and_load_schema(i, env_name:)
16
12
  old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
17
13
 
18
14
  ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
19
15
  db_config.config["database"] += "-#{i}"
20
- ActiveRecord::Tasks::DatabaseTasks.create(db_config.config)
21
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config.config, ActiveRecord::Base.schema_format, nil, env_name, db_config.spec_name)
16
+ ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config.config, ActiveRecord::Base.schema_format, nil, env_name, db_config.spec_name)
22
17
  end
23
18
  ensure
24
19
  ActiveRecord::Base.establish_connection(Rails.env.to_sym)
25
20
  ENV["VERBOSE"] = old
26
21
  end
27
-
28
- def self.drop(env_name:)
29
- old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
30
-
31
- ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
32
- ActiveRecord::Tasks::DatabaseTasks.drop(db_config.config)
33
- end
34
- ensure
35
- ENV["VERBOSE"] = old
36
- end
37
22
  end
38
23
  end
@@ -122,7 +122,7 @@ module ActiveRecord
122
122
  # Begin transactions for connections already established
123
123
  @fixture_connections = enlist_fixture_connections
124
124
  @fixture_connections.each do |connection|
125
- connection.begin_transaction joinable: false
125
+ connection.begin_transaction joinable: false, _lazy: false
126
126
  connection.pool.lock_thread = true if lock_threads
127
127
  end
128
128
 
@@ -138,7 +138,7 @@ module ActiveRecord
138
138
  end
139
139
 
140
140
  if connection && !@fixture_connections.include?(connection)
141
- connection.begin_transaction joinable: false
141
+ connection.begin_transaction joinable: false, _lazy: false
142
142
  connection.pool.lock_thread = true if lock_threads
143
143
  @fixture_connections << connection
144
144
  end
@@ -59,19 +59,26 @@ module ActiveRecord
59
59
  attribute_names.index_with(time || current_time_from_proper_timezone)
60
60
  end
61
61
 
62
- private
63
- def timestamp_attributes_for_create_in_model
64
- timestamp_attributes_for_create.select { |c| column_names.include?(c) }
65
- end
62
+ def timestamp_attributes_for_create_in_model
63
+ @timestamp_attributes_for_create_in_model ||=
64
+ (timestamp_attributes_for_create & column_names).freeze
65
+ end
66
66
 
67
- def timestamp_attributes_for_update_in_model
68
- timestamp_attributes_for_update.select { |c| column_names.include?(c) }
69
- end
67
+ def timestamp_attributes_for_update_in_model
68
+ @timestamp_attributes_for_update_in_model ||=
69
+ (timestamp_attributes_for_update & column_names).freeze
70
+ end
70
71
 
71
- def all_timestamp_attributes_in_model
72
- timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
73
- end
72
+ def all_timestamp_attributes_in_model
73
+ @all_timestamp_attributes_in_model ||=
74
+ (timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model).freeze
75
+ end
74
76
 
77
+ def current_time_from_proper_timezone
78
+ default_timezone == :utc ? Time.now.utc : Time.now
79
+ end
80
+
81
+ private
75
82
  def timestamp_attributes_for_create
76
83
  ["created_at", "created_on"]
77
84
  end
@@ -80,8 +87,11 @@ module ActiveRecord
80
87
  ["updated_at", "updated_on"]
81
88
  end
82
89
 
83
- def current_time_from_proper_timezone
84
- default_timezone == :utc ? Time.now.utc : Time.now
90
+ def reload_schema_from_cache
91
+ @timestamp_attributes_for_create_in_model = nil
92
+ @timestamp_attributes_for_update_in_model = nil
93
+ @all_timestamp_attributes_in_model = nil
94
+ super
85
95
  end
86
96
  end
87
97
 
@@ -101,8 +111,8 @@ module ActiveRecord
101
111
  super
102
112
  end
103
113
 
104
- def _update_record(*args, touch: true, **options)
105
- if touch && should_record_timestamps?
114
+ def _update_record
115
+ if @_touch_record && should_record_timestamps?
106
116
  current_time = current_time_from_proper_timezone
107
117
 
108
118
  timestamp_attributes_for_update_in_model.each do |column|
@@ -110,7 +120,13 @@ module ActiveRecord
110
120
  _write_attribute(column, current_time)
111
121
  end
112
122
  end
113
- super(*args)
123
+
124
+ super
125
+ end
126
+
127
+ def create_or_update(touch: true, **)
128
+ @_touch_record = touch
129
+ super
114
130
  end
115
131
 
116
132
  def should_record_timestamps?
@@ -118,19 +134,19 @@ module ActiveRecord
118
134
  end
119
135
 
120
136
  def timestamp_attributes_for_create_in_model
121
- self.class.send(:timestamp_attributes_for_create_in_model)
137
+ self.class.timestamp_attributes_for_create_in_model
122
138
  end
123
139
 
124
140
  def timestamp_attributes_for_update_in_model
125
- self.class.send(:timestamp_attributes_for_update_in_model)
141
+ self.class.timestamp_attributes_for_update_in_model
126
142
  end
127
143
 
128
144
  def all_timestamp_attributes_in_model
129
- self.class.send(:all_timestamp_attributes_in_model)
145
+ self.class.all_timestamp_attributes_in_model
130
146
  end
131
147
 
132
148
  def current_time_from_proper_timezone
133
- self.class.send(:current_time_from_proper_timezone)
149
+ self.class.current_time_from_proper_timezone
134
150
  end
135
151
 
136
152
  def max_updated_column_timestamp
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  # = Active Record Touch Later
5
- module TouchLater
5
+ module TouchLater # :nodoc:
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
@@ -22,7 +22,8 @@ module ActiveRecord
22
22
  @_touch_time = current_time_from_proper_timezone
23
23
 
24
24
  surreptitiously_touch @_defer_touch_attrs
25
- self.class.connection.add_transaction_record self
25
+ add_to_transaction
26
+ @_new_record_before_last_commit ||= false
26
27
 
27
28
  # touch the parents as we are not calling the after_save callbacks
28
29
  self.class.reflect_on_all_associations(:belongs_to).each do |r|
@@ -48,6 +49,7 @@ module ActiveRecord
48
49
 
49
50
  def touch_deferred_attributes
50
51
  if has_defer_touch_attrs? && persisted?
52
+ @_skip_dirty_tracking = true
51
53
  touch(*@_defer_touch_attrs, time: @_touch_time)
52
54
  @_defer_touch_attrs, @_touch_time = nil, nil
53
55
  end
@@ -234,6 +234,12 @@ module ActiveRecord
234
234
  set_callback(:commit, :after, *args, &block)
235
235
  end
236
236
 
237
+ # Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
238
+ def after_save_commit(*args, &block)
239
+ set_options_for_callbacks!(args, on: [ :create, :update ])
240
+ set_callback(:commit, :after, *args, &block)
241
+ end
242
+
237
243
  # Shortcut for <tt>after_commit :hook, on: :create</tt>.
238
244
  def after_create_commit(*args, &block)
239
245
  set_options_for_callbacks!(args, on: :create)
@@ -327,7 +333,7 @@ module ActiveRecord
327
333
  # Ensure that it is not called if the object was never persisted (failed create),
328
334
  # but call it after the commit of a destroyed object.
329
335
  def committed!(should_run_callbacks: true) #:nodoc:
330
- if should_run_callbacks && (destroyed? || persisted?)
336
+ if should_run_callbacks
331
337
  @_committed_already_called = true
332
338
  _run_commit_without_transaction_enrollment_callbacks
333
339
  _run_commit_callbacks
@@ -349,18 +355,6 @@ module ActiveRecord
349
355
  clear_transaction_record_state
350
356
  end
351
357
 
352
- # Add the record to the current transaction so that the #after_rollback and #after_commit callbacks
353
- # can be called.
354
- def add_to_transaction
355
- if has_transactional_callbacks?
356
- self.class.connection.add_transaction_record(self)
357
- else
358
- sync_with_transaction_state
359
- set_transaction_state(self.class.connection.transaction_state)
360
- end
361
- remember_transaction_record_state
362
- end
363
-
364
358
  # Executes +method+ within a transaction and captures its return value as a
365
359
  # status flag. If the status is true the transaction is committed, otherwise
366
360
  # a ROLLBACK is issued. In any case the status flag is returned.
@@ -370,29 +364,40 @@ module ActiveRecord
370
364
  def with_transaction_returning_status
371
365
  status = nil
372
366
  self.class.transaction do
373
- add_to_transaction
367
+ if has_transactional_callbacks?
368
+ add_to_transaction
369
+ else
370
+ sync_with_transaction_state if @transaction_state&.finalized?
371
+ @transaction_state = self.class.connection.transaction_state
372
+ end
373
+ remember_transaction_record_state
374
+
374
375
  status = yield
375
376
  raise ActiveRecord::Rollback unless status
376
377
  end
377
378
  status
378
379
  end
379
380
 
381
+ def trigger_transactional_callbacks? # :nodoc:
382
+ (@_new_record_before_last_commit || _trigger_update_callback) && persisted? ||
383
+ _trigger_destroy_callback && destroyed?
384
+ end
385
+
380
386
  private
381
387
  attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
382
388
 
383
389
  # Save the new record state and id of a record so it can be restored later if a transaction fails.
384
390
  def remember_transaction_record_state
385
- @_start_transaction_state.reverse_merge!(
391
+ @_start_transaction_state ||= {
386
392
  id: id,
387
393
  new_record: @new_record,
388
394
  destroyed: @destroyed,
395
+ attributes: @attributes,
389
396
  frozen?: frozen?,
390
- )
391
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
392
- remember_new_record_before_last_commit
393
- end
397
+ level: 0
398
+ }
399
+ @_start_transaction_state[:level] += 1
394
400
 
395
- def remember_new_record_before_last_commit
396
401
  if _committed_already_called
397
402
  @_new_record_before_last_commit = false
398
403
  else
@@ -402,27 +407,32 @@ module ActiveRecord
402
407
 
403
408
  # Clear the new record state and id of a record.
404
409
  def clear_transaction_record_state
405
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
410
+ return unless @_start_transaction_state
411
+ @_start_transaction_state[:level] -= 1
406
412
  force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
407
413
  end
408
414
 
409
415
  # Force to clear the transaction record state.
410
416
  def force_clear_transaction_record_state
411
- @_start_transaction_state.clear
417
+ @_start_transaction_state = nil
418
+ @transaction_state = nil
412
419
  end
413
420
 
414
421
  # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
415
- def restore_transaction_record_state(force = false)
416
- unless @_start_transaction_state.empty?
417
- transaction_level = (@_start_transaction_state[:level] || 0) - 1
418
- if transaction_level < 1 || force
419
- restore_state = @_start_transaction_state
420
- thaw
422
+ def restore_transaction_record_state(force_restore_state = false)
423
+ if restore_state = @_start_transaction_state
424
+ if force_restore_state || restore_state[:level] <= 1
421
425
  @new_record = restore_state[:new_record]
422
426
  @destroyed = restore_state[:destroyed]
423
- pk = self.class.primary_key
424
- if pk && _read_attribute(pk) != restore_state[:id]
425
- _write_attribute(pk, restore_state[:id])
427
+ @attributes = restore_state[:attributes].map do |attr|
428
+ value = @attributes.fetch_value(attr.name)
429
+ attr = attr.with_value_from_user(value) if attr.value != value
430
+ attr
431
+ end
432
+ @mutations_from_database = nil
433
+ @mutations_before_last_save = nil
434
+ if @attributes.fetch_value(@primary_key) != restore_state[:id]
435
+ @attributes.write_from_user(@primary_key, restore_state[:id])
426
436
  end
427
437
  freeze if restore_state[:frozen?]
428
438
  end
@@ -443,8 +453,10 @@ module ActiveRecord
443
453
  end
444
454
  end
445
455
 
446
- def set_transaction_state(state)
447
- @transaction_state = state
456
+ # Add the record to the current transaction so that the #after_rollback and #after_commit
457
+ # callbacks can be called.
458
+ def add_to_transaction
459
+ self.class.connection.add_transaction_record(self)
448
460
  end
449
461
 
450
462
  def has_transactional_callbacks?
@@ -464,19 +476,17 @@ module ActiveRecord
464
476
  # This method checks to see if the ActiveRecord object's state reflects
465
477
  # the TransactionState, and rolls back or commits the Active Record object
466
478
  # as appropriate.
467
- #
468
- # Since Active Record objects can be inside multiple transactions, this
469
- # method recursively goes through the parent of the TransactionState and
470
- # checks if the Active Record object reflects the state of the object.
471
479
  def sync_with_transaction_state
472
- update_attributes_from_transaction_state(@transaction_state)
473
- end
474
-
475
- def update_attributes_from_transaction_state(transaction_state)
476
- if transaction_state && transaction_state.finalized?
477
- restore_transaction_record_state(transaction_state.fully_rolledback?) if transaction_state.rolledback?
478
- force_clear_transaction_record_state if transaction_state.fully_committed?
479
- clear_transaction_record_state if transaction_state.fully_completed?
480
+ if transaction_state = @transaction_state
481
+ if transaction_state.fully_committed?
482
+ force_clear_transaction_record_state
483
+ elsif transaction_state.committed?
484
+ clear_transaction_record_state
485
+ elsif transaction_state.rolledback?
486
+ force_restore_state = transaction_state.fully_rolledback?
487
+ restore_transaction_record_state(force_restore_state)
488
+ clear_transaction_record_state
489
+ end
480
490
  end
481
491
  end
482
492
  end
@@ -8,21 +8,27 @@ module ActiveRecord
8
8
  @table_name = table_name
9
9
  end
10
10
 
11
- def type_cast_for_database(attribute_name, value)
11
+ def type_cast_for_database(attr_name, value)
12
12
  return value if value.is_a?(Arel::Nodes::BindParam)
13
- column = column_for(attribute_name)
14
- connection.type_cast_from_column(column, value)
13
+ type = type_for_attribute(attr_name)
14
+ type.serialize(value)
15
15
  end
16
16
 
17
- private
18
- attr_reader :table_name
19
- delegate :connection, to: :@klass
17
+ def type_for_attribute(attr_name)
18
+ schema_cache = connection.schema_cache
20
19
 
21
- def column_for(attribute_name)
22
- if connection.schema_cache.data_source_exists?(table_name)
23
- connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
24
- end
20
+ if schema_cache.data_source_exists?(table_name)
21
+ column = schema_cache.columns_hash(table_name)[attr_name.to_s]
22
+ type = connection.lookup_cast_type_from_column(column) if column
25
23
  end
24
+
25
+ type || Type.default_value
26
+ end
27
+
28
+ delegate :connection, to: :@klass, private: true
29
+
30
+ private
31
+ attr_reader :table_name
26
32
  end
27
33
  end
28
34
  end
@@ -12,7 +12,7 @@ module ActiveRecord
12
12
  raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \
13
13
  "Pass a symbol or an array of symbols instead: `scope: :user_id`"
14
14
  end
15
- super({ case_sensitive: true }.merge!(options))
15
+ super
16
16
  @klass = options[:class]
17
17
  end
18
18
 
@@ -25,7 +25,7 @@ module ActiveRecord
25
25
  if finder_class.primary_key
26
26
  relation = relation.where.not(finder_class.primary_key => record.id_in_database)
27
27
  else
28
- raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
28
+ raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.")
29
29
  end
30
30
  end
31
31
  relation = scope_relation(record, relation)
@@ -60,8 +60,8 @@ module ActiveRecord
60
60
  comparison = relation.bind_attribute(attribute, value) do |attr, bind|
61
61
  return relation.none! if bind.unboundable?
62
62
 
63
- if bind.nil?
64
- attr.eq(bind)
63
+ if !options.key?(:case_sensitive) || bind.nil?
64
+ klass.connection.default_uniqueness_comparison(attr, bind, klass)
65
65
  elsif options[:case_sensitive]
66
66
  klass.connection.case_sensitive_comparison(attr, bind)
67
67
  else
@@ -40,6 +40,7 @@ module ActiveRecord
40
40
  include ActiveModel::Validations
41
41
 
42
42
  # The validation process on save can be skipped by passing <tt>validate: false</tt>.
43
+ # The validation context can be changed by passing <tt>context: context</tt>.
43
44
  # The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced
44
45
  # with this when the validations module is mixed in, which it is by default.
45
46
  def save(options = {})