activerecord 7.2.0 → 8.0.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +189 -745
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +9 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attributes.rb +6 -5
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +23 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +53 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +44 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_handling.rb +22 -0
- data/lib/active_record/core.rb +16 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +16 -9
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +8 -0
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -1
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -33
- data/lib/active_record/model_schema.rb +6 -3
- data/lib/active_record/nested_attributes.rb +11 -2
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +97 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +6 -6
- data/lib/active_record/railtie.rb +8 -14
- data/lib/active_record/reflection.rb +19 -10
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +135 -75
- data/lib/active_record/relation/calculations.rb +24 -19
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +6 -1
- data/lib/active_record/relation/query_methods.rb +58 -37
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +72 -61
- data/lib/active_record/result.rb +68 -7
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/statement_cache.rb +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -16
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +9 -8
- data/lib/active_record.rb +15 -0
- data/lib/arel/collectors/bind.rb +1 -1
- metadata +14 -14
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "benchmark"
|
4
3
|
require "set"
|
5
|
-
require "zlib"
|
6
4
|
require "active_support/core_ext/array/access"
|
7
5
|
require "active_support/core_ext/enumerable"
|
8
6
|
require "active_support/core_ext/module/attribute_accessors"
|
@@ -21,7 +19,7 @@ module ActiveRecord
|
|
21
19
|
# For example the following migration is not reversible.
|
22
20
|
# Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error.
|
23
21
|
#
|
24
|
-
# class IrreversibleMigrationExample < ActiveRecord::Migration[
|
22
|
+
# class IrreversibleMigrationExample < ActiveRecord::Migration[8.0]
|
25
23
|
# def change
|
26
24
|
# create_table :distributors do |t|
|
27
25
|
# t.string :zipcode
|
@@ -39,7 +37,7 @@ module ActiveRecord
|
|
39
37
|
#
|
40
38
|
# 1. Define <tt>#up</tt> and <tt>#down</tt> methods instead of <tt>#change</tt>:
|
41
39
|
#
|
42
|
-
# class ReversibleMigrationExample < ActiveRecord::Migration[
|
40
|
+
# class ReversibleMigrationExample < ActiveRecord::Migration[8.0]
|
43
41
|
# def up
|
44
42
|
# create_table :distributors do |t|
|
45
43
|
# t.string :zipcode
|
@@ -64,7 +62,7 @@ module ActiveRecord
|
|
64
62
|
#
|
65
63
|
# 2. Use the #reversible method in <tt>#change</tt> method:
|
66
64
|
#
|
67
|
-
# class ReversibleMigrationExample < ActiveRecord::Migration[
|
65
|
+
# class ReversibleMigrationExample < ActiveRecord::Migration[8.0]
|
68
66
|
# def change
|
69
67
|
# create_table :distributors do |t|
|
70
68
|
# t.string :zipcode
|
@@ -250,7 +248,7 @@ module ActiveRecord
|
|
250
248
|
#
|
251
249
|
# Example of a simple migration:
|
252
250
|
#
|
253
|
-
# class AddSsl < ActiveRecord::Migration[
|
251
|
+
# class AddSsl < ActiveRecord::Migration[8.0]
|
254
252
|
# def up
|
255
253
|
# add_column :accounts, :ssl_enabled, :boolean, default: true
|
256
254
|
# end
|
@@ -270,7 +268,7 @@ module ActiveRecord
|
|
270
268
|
#
|
271
269
|
# Example of a more complex migration that also needs to initialize data:
|
272
270
|
#
|
273
|
-
# class AddSystemSettings < ActiveRecord::Migration[
|
271
|
+
# class AddSystemSettings < ActiveRecord::Migration[8.0]
|
274
272
|
# def up
|
275
273
|
# create_table :system_settings do |t|
|
276
274
|
# t.string :name
|
@@ -357,7 +355,7 @@ module ActiveRecord
|
|
357
355
|
#
|
358
356
|
# === Deletion
|
359
357
|
#
|
360
|
-
# * <tt>drop_table(
|
358
|
+
# * <tt>drop_table(*names)</tt>: Drops the given tables.
|
361
359
|
# * <tt>drop_join_table(table_1, table_2, options)</tt>: Drops the join table
|
362
360
|
# specified by the given arguments.
|
363
361
|
# * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
|
@@ -399,7 +397,7 @@ module ActiveRecord
|
|
399
397
|
# $ bin/rails generate migration add_fieldname_to_tablename fieldname:string
|
400
398
|
#
|
401
399
|
# This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
|
402
|
-
# class AddFieldnameToTablename < ActiveRecord::Migration[
|
400
|
+
# class AddFieldnameToTablename < ActiveRecord::Migration[8.0]
|
403
401
|
# def change
|
404
402
|
# add_column :tablenames, :fieldname, :string
|
405
403
|
# end
|
@@ -425,7 +423,7 @@ module ActiveRecord
|
|
425
423
|
#
|
426
424
|
# Not all migrations change the schema. Some just fix the data:
|
427
425
|
#
|
428
|
-
# class RemoveEmptyTags < ActiveRecord::Migration[
|
426
|
+
# class RemoveEmptyTags < ActiveRecord::Migration[8.0]
|
429
427
|
# def up
|
430
428
|
# Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
|
431
429
|
# end
|
@@ -438,7 +436,7 @@ module ActiveRecord
|
|
438
436
|
#
|
439
437
|
# Others remove columns when they migrate up instead of down:
|
440
438
|
#
|
441
|
-
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[
|
439
|
+
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[8.0]
|
442
440
|
# def up
|
443
441
|
# remove_column :items, :incomplete_items_count
|
444
442
|
# remove_column :items, :completed_items_count
|
@@ -452,7 +450,7 @@ module ActiveRecord
|
|
452
450
|
#
|
453
451
|
# And sometimes you need to do something in SQL not abstracted directly by migrations:
|
454
452
|
#
|
455
|
-
# class MakeJoinUnique < ActiveRecord::Migration[
|
453
|
+
# class MakeJoinUnique < ActiveRecord::Migration[8.0]
|
456
454
|
# def up
|
457
455
|
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
|
458
456
|
# end
|
@@ -469,7 +467,7 @@ module ActiveRecord
|
|
469
467
|
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
|
470
468
|
# latest column data from after the new column was added. Example:
|
471
469
|
#
|
472
|
-
# class AddPeopleSalary < ActiveRecord::Migration[
|
470
|
+
# class AddPeopleSalary < ActiveRecord::Migration[8.0]
|
473
471
|
# def up
|
474
472
|
# add_column :people, :salary, :integer
|
475
473
|
# Person.reset_column_information
|
@@ -531,7 +529,7 @@ module ActiveRecord
|
|
531
529
|
# To define a reversible migration, define the +change+ method in your
|
532
530
|
# migration like this:
|
533
531
|
#
|
534
|
-
# class TenderloveMigration < ActiveRecord::Migration[
|
532
|
+
# class TenderloveMigration < ActiveRecord::Migration[8.0]
|
535
533
|
# def change
|
536
534
|
# create_table(:horses) do |t|
|
537
535
|
# t.column :content, :text
|
@@ -561,7 +559,7 @@ module ActiveRecord
|
|
561
559
|
# can't execute inside a transaction though, and for these situations
|
562
560
|
# you can turn the automatic transactions off.
|
563
561
|
#
|
564
|
-
# class ChangeEnum < ActiveRecord::Migration[
|
562
|
+
# class ChangeEnum < ActiveRecord::Migration[8.0]
|
565
563
|
# disable_ddl_transaction!
|
566
564
|
#
|
567
565
|
# def up
|
@@ -604,7 +602,7 @@ module ActiveRecord
|
|
604
602
|
end
|
605
603
|
end
|
606
604
|
|
607
|
-
def drop_table(
|
605
|
+
def drop_table(*table_names, **options)
|
608
606
|
if block_given?
|
609
607
|
super { |t| yield compatible_table_definition(t) }
|
610
608
|
else
|
@@ -715,13 +713,7 @@ module ActiveRecord
|
|
715
713
|
|
716
714
|
def load_schema_if_pending!
|
717
715
|
if any_schema_needs_update?
|
718
|
-
|
719
|
-
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
|
720
|
-
|
721
|
-
FileUtils.cd(root) do
|
722
|
-
Base.connection_handler.clear_all_connections!(:all)
|
723
|
-
system("bin/rails db:test:prepare")
|
724
|
-
end
|
716
|
+
load_schema!
|
725
717
|
end
|
726
718
|
|
727
719
|
check_pending_migrations
|
@@ -785,6 +777,16 @@ module ActiveRecord
|
|
785
777
|
def env
|
786
778
|
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
|
787
779
|
end
|
780
|
+
|
781
|
+
def load_schema!
|
782
|
+
# Roundtrip to Rake to allow plugins to hook into database initialization.
|
783
|
+
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
|
784
|
+
|
785
|
+
FileUtils.cd(root) do
|
786
|
+
Base.connection_handler.clear_all_connections!(:all)
|
787
|
+
system("bin/rails db:test:prepare")
|
788
|
+
end
|
789
|
+
end
|
788
790
|
end
|
789
791
|
|
790
792
|
def disable_ddl_transaction # :nodoc:
|
@@ -822,7 +824,7 @@ module ActiveRecord
|
|
822
824
|
# and create the table 'apples' on the way up, and the reverse
|
823
825
|
# on the way down.
|
824
826
|
#
|
825
|
-
# class FixTLMigration < ActiveRecord::Migration[
|
827
|
+
# class FixTLMigration < ActiveRecord::Migration[8.0]
|
826
828
|
# def change
|
827
829
|
# revert do
|
828
830
|
# create_table(:horses) do |t|
|
@@ -841,7 +843,7 @@ module ActiveRecord
|
|
841
843
|
#
|
842
844
|
# require_relative "20121212123456_tenderlove_migration"
|
843
845
|
#
|
844
|
-
# class FixupTLMigration < ActiveRecord::Migration[
|
846
|
+
# class FixupTLMigration < ActiveRecord::Migration[8.0]
|
845
847
|
# def change
|
846
848
|
# revert TenderloveMigration
|
847
849
|
#
|
@@ -892,7 +894,7 @@ module ActiveRecord
|
|
892
894
|
# when the three columns 'first_name', 'last_name' and 'full_name' exist,
|
893
895
|
# even when migrating down:
|
894
896
|
#
|
895
|
-
# class SplitNameMigration < ActiveRecord::Migration[
|
897
|
+
# class SplitNameMigration < ActiveRecord::Migration[8.0]
|
896
898
|
# def change
|
897
899
|
# add_column :users, :first_name, :string
|
898
900
|
# add_column :users, :last_name, :string
|
@@ -920,7 +922,7 @@ module ActiveRecord
|
|
920
922
|
# In the following example, the new column +published+ will be given
|
921
923
|
# the value +true+ for all existing records.
|
922
924
|
#
|
923
|
-
# class AddPublishedToPosts < ActiveRecord::Migration[
|
925
|
+
# class AddPublishedToPosts < ActiveRecord::Migration[8.0]
|
924
926
|
# def change
|
925
927
|
# add_column :posts, :published, :boolean, default: false
|
926
928
|
# up_only do
|
@@ -972,16 +974,16 @@ module ActiveRecord
|
|
972
974
|
when :down then announce "reverting"
|
973
975
|
end
|
974
976
|
|
975
|
-
|
977
|
+
time_elapsed = nil
|
976
978
|
ActiveRecord::Tasks::DatabaseTasks.migration_connection.pool.with_connection do |conn|
|
977
|
-
|
979
|
+
time_elapsed = ActiveSupport::Benchmark.realtime do
|
978
980
|
exec_migration(conn, direction)
|
979
981
|
end
|
980
982
|
end
|
981
983
|
|
982
984
|
case direction
|
983
|
-
when :up then announce "migrated (%.4fs)" %
|
984
|
-
when :down then announce "reverted (%.4fs)" %
|
985
|
+
when :up then announce "migrated (%.4fs)" % time_elapsed; write
|
986
|
+
when :down then announce "reverted (%.4fs)" % time_elapsed; write
|
985
987
|
end
|
986
988
|
end
|
987
989
|
|
@@ -1022,8 +1024,8 @@ module ActiveRecord
|
|
1022
1024
|
def say_with_time(message)
|
1023
1025
|
say(message)
|
1024
1026
|
result = nil
|
1025
|
-
|
1026
|
-
say "%.4fs" %
|
1027
|
+
time_elapsed = ActiveSupport::Benchmark.realtime { result = yield }
|
1028
|
+
say "%.4fs" % time_elapsed, :subitem
|
1027
1029
|
say("#{result} rows", :subitem) if result.is_a?(Integer)
|
1028
1030
|
result
|
1029
1031
|
end
|
@@ -431,7 +431,6 @@ module ActiveRecord
|
|
431
431
|
end
|
432
432
|
|
433
433
|
def columns
|
434
|
-
load_schema unless @columns
|
435
434
|
@columns ||= columns_hash.values.freeze
|
436
435
|
end
|
437
436
|
|
@@ -503,7 +502,7 @@ module ActiveRecord
|
|
503
502
|
# when just after creating a table you want to populate it with some default
|
504
503
|
# values, e.g.:
|
505
504
|
#
|
506
|
-
# class CreateJobLevels < ActiveRecord::Migration[
|
505
|
+
# class CreateJobLevels < ActiveRecord::Migration[8.0]
|
507
506
|
# def up
|
508
507
|
# create_table :job_levels do |t|
|
509
508
|
# t.integer :id
|
@@ -531,7 +530,9 @@ module ActiveRecord
|
|
531
530
|
initialize_find_by_cache
|
532
531
|
end
|
533
532
|
|
534
|
-
|
533
|
+
# Load the model's schema information either from the schema cache
|
534
|
+
# or directly from the database.
|
535
|
+
def load_schema
|
535
536
|
return if schema_loaded?
|
536
537
|
@load_schema_monitor.synchronize do
|
537
538
|
return if schema_loaded?
|
@@ -592,6 +593,8 @@ module ActiveRecord
|
|
592
593
|
columns_hash = schema_cache.columns_hash(table_name)
|
593
594
|
columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
|
594
595
|
@columns_hash = columns_hash.freeze
|
596
|
+
|
597
|
+
_default_attributes # Precompute to cache DB-dependent attribute types
|
595
598
|
end
|
596
599
|
|
597
600
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
@@ -524,12 +524,12 @@ module ActiveRecord
|
|
524
524
|
unless reject_new_record?(association_name, attributes)
|
525
525
|
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
526
526
|
end
|
527
|
-
elsif existing_record =
|
527
|
+
elsif existing_record = find_record_by_id(association.klass, existing_records, attributes["id"])
|
528
528
|
unless call_reject_if(association_name, attributes)
|
529
529
|
# Make sure we are operating on the actual object which is in the association's
|
530
530
|
# proxy_target array (either by finding it, or adding it if not found)
|
531
531
|
# Take into account that the proxy_target may have changed due to callbacks
|
532
|
-
target_record = association.
|
532
|
+
target_record = find_record_by_id(association.klass, association.target, attributes["id"])
|
533
533
|
if target_record
|
534
534
|
existing_record = target_record
|
535
535
|
else
|
@@ -620,5 +620,14 @@ module ActiveRecord
|
|
620
620
|
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
|
621
621
|
model, "id", record_id)
|
622
622
|
end
|
623
|
+
|
624
|
+
def find_record_by_id(klass, records, id)
|
625
|
+
if klass.composite_primary_key?
|
626
|
+
id = Array(id).map(&:to_s)
|
627
|
+
records.find { |record| Array(record.id).map(&:to_s) == id }
|
628
|
+
else
|
629
|
+
records.find { |record| record.id.to_s == id.to_s }
|
630
|
+
end
|
631
|
+
end
|
623
632
|
end
|
624
633
|
end
|
@@ -248,18 +248,16 @@ module ActiveRecord
|
|
248
248
|
|
249
249
|
im = Arel::InsertManager.new(arel_table)
|
250
250
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
im.insert(values.transform_keys { |name| arel_table[name] })
|
256
|
-
end
|
257
|
-
|
258
|
-
connection.insert(
|
259
|
-
im, "#{self} Create", primary_key || false, primary_key_value,
|
260
|
-
returning: returning
|
261
|
-
)
|
251
|
+
if values.empty?
|
252
|
+
im.insert(connection.empty_insert_statement_value(primary_key))
|
253
|
+
else
|
254
|
+
im.insert(values.transform_keys { |name| arel_table[name] })
|
262
255
|
end
|
256
|
+
|
257
|
+
connection.insert(
|
258
|
+
im, "#{self} Create", primary_key || false, primary_key_value,
|
259
|
+
returning: returning
|
260
|
+
)
|
263
261
|
end
|
264
262
|
|
265
263
|
def _update_record(values, constraints) # :nodoc:
|
@@ -812,159 +810,159 @@ module ActiveRecord
|
|
812
810
|
end
|
813
811
|
end
|
814
812
|
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
813
|
+
private
|
814
|
+
def init_internals
|
815
|
+
super
|
816
|
+
@_trigger_destroy_callback = @_trigger_update_callback = nil
|
817
|
+
@previously_new_record = false
|
818
|
+
end
|
821
819
|
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
820
|
+
def strict_loaded_associations
|
821
|
+
@association_cache.find_all do |_, assoc|
|
822
|
+
assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only?
|
823
|
+
end.map(&:first)
|
824
|
+
end
|
827
825
|
|
828
|
-
|
829
|
-
|
830
|
-
|
826
|
+
def _find_record(options)
|
827
|
+
all_queries = options ? options[:all_queries] : nil
|
828
|
+
base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations)
|
831
829
|
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
830
|
+
if options && options[:lock]
|
831
|
+
base.lock(options[:lock]).find_by!(_in_memory_query_constraints_hash)
|
832
|
+
else
|
833
|
+
base.find_by!(_in_memory_query_constraints_hash)
|
834
|
+
end
|
836
835
|
end
|
837
|
-
end
|
838
836
|
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
837
|
+
def _in_memory_query_constraints_hash
|
838
|
+
if self.class.query_constraints_list.nil?
|
839
|
+
{ @primary_key => id }
|
840
|
+
else
|
841
|
+
self.class.query_constraints_list.index_with do |column_name|
|
842
|
+
attribute(column_name)
|
843
|
+
end
|
845
844
|
end
|
846
845
|
end
|
847
|
-
end
|
848
846
|
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
847
|
+
def apply_scoping?(options)
|
848
|
+
!(options && options[:unscoped]) &&
|
849
|
+
(self.class.default_scopes?(all_queries: true) || self.class.global_current_scope)
|
850
|
+
end
|
853
851
|
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
852
|
+
def _query_constraints_hash
|
853
|
+
if self.class.query_constraints_list.nil?
|
854
|
+
{ @primary_key => id_in_database }
|
855
|
+
else
|
856
|
+
self.class.query_constraints_list.index_with do |column_name|
|
857
|
+
attribute_in_database(column_name)
|
858
|
+
end
|
860
859
|
end
|
861
860
|
end
|
862
|
-
end
|
863
861
|
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
def destroy_row
|
869
|
-
_delete_row
|
870
|
-
end
|
871
|
-
|
872
|
-
def _delete_row
|
873
|
-
self.class._delete_record(_query_constraints_hash)
|
874
|
-
end
|
862
|
+
# A hook to be overridden by association modules.
|
863
|
+
def destroy_associations
|
864
|
+
end
|
875
865
|
|
876
|
-
|
877
|
-
|
866
|
+
def destroy_row
|
867
|
+
_delete_row
|
868
|
+
end
|
878
869
|
|
879
|
-
|
880
|
-
|
870
|
+
def _delete_row
|
871
|
+
self.class._delete_record(_query_constraints_hash)
|
881
872
|
end
|
882
873
|
|
883
|
-
|
884
|
-
|
874
|
+
def _touch_row(attribute_names, time)
|
875
|
+
time ||= current_time_from_proper_timezone
|
885
876
|
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
_query_constraints_hash
|
890
|
-
)
|
891
|
-
end
|
877
|
+
attribute_names.each do |attr_name|
|
878
|
+
_write_attribute(attr_name, time)
|
879
|
+
end
|
892
880
|
|
893
|
-
|
894
|
-
|
895
|
-
return false if destroyed?
|
896
|
-
result = new_record? ? _create_record(&block) : _update_record(&block)
|
897
|
-
result != false
|
898
|
-
end
|
881
|
+
_update_row(attribute_names, "touch")
|
882
|
+
end
|
899
883
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
884
|
+
def _update_row(attribute_names, attempted_action = "update")
|
885
|
+
self.class._update_record(
|
886
|
+
attributes_with_values(attribute_names),
|
887
|
+
_query_constraints_hash
|
888
|
+
)
|
889
|
+
end
|
904
890
|
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
@_trigger_update_callback = affected_rows == 1
|
891
|
+
def create_or_update(**, &block)
|
892
|
+
_raise_readonly_record_error if readonly?
|
893
|
+
return false if destroyed?
|
894
|
+
result = new_record? ? _create_record(&block) : _update_record(&block)
|
895
|
+
result != false
|
911
896
|
end
|
912
897
|
|
913
|
-
|
898
|
+
# Updates the associated record with values matching those of the instance attributes.
|
899
|
+
# Returns the number of affected rows.
|
900
|
+
def _update_record(attribute_names = self.attribute_names)
|
901
|
+
attribute_names = attributes_for_update(attribute_names)
|
914
902
|
|
915
|
-
|
903
|
+
if attribute_names.empty?
|
904
|
+
affected_rows = 0
|
905
|
+
@_trigger_update_callback = true
|
906
|
+
else
|
907
|
+
affected_rows = _update_row(attribute_names)
|
908
|
+
@_trigger_update_callback = affected_rows == 1
|
909
|
+
end
|
916
910
|
|
917
|
-
|
918
|
-
end
|
911
|
+
@previously_new_record = false
|
919
912
|
|
920
|
-
|
921
|
-
# and returns its id.
|
922
|
-
def _create_record(attribute_names = self.attribute_names)
|
923
|
-
attribute_names = attributes_for_create(attribute_names)
|
913
|
+
yield(self) if block_given?
|
924
914
|
|
925
|
-
|
926
|
-
|
915
|
+
affected_rows
|
916
|
+
end
|
927
917
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
)
|
918
|
+
# Creates a record with values matching those of the instance attributes
|
919
|
+
# and returns its id.
|
920
|
+
def _create_record(attribute_names = self.attribute_names)
|
921
|
+
attribute_names = attributes_for_create(attribute_names)
|
933
922
|
|
934
|
-
|
935
|
-
|
936
|
-
end if returning_values
|
937
|
-
end
|
923
|
+
self.class.with_connection do |connection|
|
924
|
+
returning_columns = self.class._returning_columns_for_insert(connection)
|
938
925
|
|
939
|
-
|
940
|
-
|
926
|
+
returning_values = self.class._insert_record(
|
927
|
+
connection,
|
928
|
+
attributes_with_values(attribute_names),
|
929
|
+
returning_columns
|
930
|
+
)
|
941
931
|
|
942
|
-
|
932
|
+
returning_columns.zip(returning_values).each do |column, value|
|
933
|
+
_write_attribute(column, value) if !_read_attribute(column)
|
934
|
+
end if returning_values
|
935
|
+
end
|
943
936
|
|
944
|
-
|
945
|
-
|
937
|
+
@new_record = false
|
938
|
+
@previously_new_record = true
|
946
939
|
|
947
|
-
|
948
|
-
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
|
949
|
-
end
|
940
|
+
yield(self) if block_given?
|
950
941
|
|
951
|
-
|
952
|
-
|
953
|
-
key = self.class.primary_key
|
954
|
-
raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
|
955
|
-
ensure
|
956
|
-
@_association_destroy_exception = nil
|
957
|
-
end
|
942
|
+
id
|
943
|
+
end
|
958
944
|
|
959
|
-
|
960
|
-
|
961
|
-
|
945
|
+
def verify_readonly_attribute(name)
|
946
|
+
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
|
947
|
+
end
|
962
948
|
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
949
|
+
def _raise_record_not_destroyed
|
950
|
+
@_association_destroy_exception ||= nil
|
951
|
+
key = self.class.primary_key
|
952
|
+
raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
|
953
|
+
ensure
|
954
|
+
@_association_destroy_exception = nil
|
955
|
+
end
|
956
|
+
|
957
|
+
def _raise_readonly_record_error
|
958
|
+
raise ReadOnlyRecord, "#{self.class} is marked as readonly"
|
959
|
+
end
|
960
|
+
|
961
|
+
def _raise_record_not_touched_error
|
962
|
+
raise ActiveRecordError, <<~MSG.squish
|
963
|
+
Cannot touch on a new or destroyed record object. Consider using
|
964
|
+
persisted?, new_record?, or destroyed? before touching.
|
965
|
+
MSG
|
966
|
+
end
|
969
967
|
end
|
970
968
|
end
|