activerecord 7.2.3 → 8.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +612 -1055
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +45 -12
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +52 -40
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +40 -24
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +140 -86
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +107 -75
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +18 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +37 -16
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +84 -49
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -13
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -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:
|
|
@@ -494,6 +492,7 @@ module ActiveRecord
|
|
|
494
492
|
becoming.instance_variable_set(:@attributes, @attributes)
|
|
495
493
|
becoming.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
|
|
496
494
|
becoming.instance_variable_set(:@new_record, new_record?)
|
|
495
|
+
becoming.instance_variable_set(:@previously_new_record, previously_new_record?)
|
|
497
496
|
becoming.instance_variable_set(:@destroyed, destroyed?)
|
|
498
497
|
becoming.errors.copy!(errors)
|
|
499
498
|
end
|
|
@@ -583,8 +582,8 @@ module ActiveRecord
|
|
|
583
582
|
end
|
|
584
583
|
|
|
585
584
|
# Equivalent to <code>update_columns(name => value)</code>.
|
|
586
|
-
def update_column(name, value)
|
|
587
|
-
update_columns(name => value)
|
|
585
|
+
def update_column(name, value, touch: nil)
|
|
586
|
+
update_columns(name => value, touch: touch)
|
|
588
587
|
end
|
|
589
588
|
|
|
590
589
|
# Updates the attributes directly in the database issuing an UPDATE SQL
|
|
@@ -598,11 +597,25 @@ module ActiveRecord
|
|
|
598
597
|
#
|
|
599
598
|
# * \Validations are skipped.
|
|
600
599
|
# * \Callbacks are skipped.
|
|
601
|
-
# * +updated_at+/+updated_on+ are
|
|
600
|
+
# * +updated_at+/+updated_on+ are updated if the +touch+ option is set to +true+.
|
|
602
601
|
# * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
|
|
603
602
|
#
|
|
604
603
|
# This method raises an ActiveRecord::ActiveRecordError when called on new
|
|
605
604
|
# objects, or when at least one of the attributes is marked as readonly.
|
|
605
|
+
#
|
|
606
|
+
# ==== Parameters
|
|
607
|
+
#
|
|
608
|
+
# * <tt>:touch</tt> option - Touch the timestamp columns when updating.
|
|
609
|
+
# * If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
|
610
|
+
#
|
|
611
|
+
# ==== Examples
|
|
612
|
+
#
|
|
613
|
+
# # Update a single attribute.
|
|
614
|
+
# user.update_columns(last_request_at: Time.current)
|
|
615
|
+
#
|
|
616
|
+
# # Update with touch option.
|
|
617
|
+
# user.update_columns(last_request_at: Time.current, touch: true)
|
|
618
|
+
|
|
606
619
|
def update_columns(attributes)
|
|
607
620
|
raise ActiveRecordError, "cannot update a new record" if new_record?
|
|
608
621
|
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
|
|
@@ -614,6 +627,15 @@ module ActiveRecord
|
|
|
614
627
|
verify_readonly_attribute(name) || name
|
|
615
628
|
end
|
|
616
629
|
|
|
630
|
+
touch = attributes.delete("touch")
|
|
631
|
+
if touch
|
|
632
|
+
names = touch if touch != true
|
|
633
|
+
names = Array.wrap(names)
|
|
634
|
+
options = names.extract_options!
|
|
635
|
+
touch_updates = self.class.touch_attributes_with_time(*names, **options)
|
|
636
|
+
attributes.with_defaults!(touch_updates) unless touch_updates.empty?
|
|
637
|
+
end
|
|
638
|
+
|
|
617
639
|
update_constraints = _query_constraints_hash
|
|
618
640
|
attributes = attributes.each_with_object({}) do |(k, v), h|
|
|
619
641
|
h[k] = @attributes.write_cast_value(k, v)
|
|
@@ -642,8 +664,15 @@ module ActiveRecord
|
|
|
642
664
|
# This means that any other modified attributes will still be dirty.
|
|
643
665
|
# Validations and callbacks are skipped. Supports the +touch+ option from
|
|
644
666
|
# +update_counters+, see that for more.
|
|
667
|
+
#
|
|
668
|
+
# This method raises an ActiveRecord::ActiveRecordError when called on new
|
|
669
|
+
# objects, or when at least one of the attributes is marked as readonly.
|
|
670
|
+
#
|
|
645
671
|
# Returns +self+.
|
|
646
672
|
def increment!(attribute, by = 1, touch: nil)
|
|
673
|
+
raise ActiveRecordError, "cannot update a new record" if new_record?
|
|
674
|
+
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
|
|
675
|
+
|
|
647
676
|
increment(attribute, by)
|
|
648
677
|
change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
|
|
649
678
|
self.class.update_counters(id, attribute => change, touch: touch)
|
|
@@ -812,159 +841,159 @@ module ActiveRecord
|
|
|
812
841
|
end
|
|
813
842
|
end
|
|
814
843
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
844
|
+
private
|
|
845
|
+
def init_internals
|
|
846
|
+
super
|
|
847
|
+
@_trigger_destroy_callback = @_trigger_update_callback = nil
|
|
848
|
+
@previously_new_record = false
|
|
849
|
+
end
|
|
821
850
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
851
|
+
def strict_loaded_associations
|
|
852
|
+
@association_cache.find_all do |_, assoc|
|
|
853
|
+
assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only?
|
|
854
|
+
end.map(&:first)
|
|
855
|
+
end
|
|
827
856
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
857
|
+
def _find_record(options)
|
|
858
|
+
all_queries = options ? options[:all_queries] : nil
|
|
859
|
+
base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations)
|
|
831
860
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
861
|
+
if options && options[:lock]
|
|
862
|
+
base.lock(options[:lock]).find_by!(_in_memory_query_constraints_hash)
|
|
863
|
+
else
|
|
864
|
+
base.find_by!(_in_memory_query_constraints_hash)
|
|
865
|
+
end
|
|
836
866
|
end
|
|
837
|
-
end
|
|
838
867
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
868
|
+
def _in_memory_query_constraints_hash
|
|
869
|
+
if self.class.query_constraints_list.nil?
|
|
870
|
+
{ @primary_key => id }
|
|
871
|
+
else
|
|
872
|
+
self.class.query_constraints_list.index_with do |column_name|
|
|
873
|
+
attribute(column_name)
|
|
874
|
+
end
|
|
845
875
|
end
|
|
846
876
|
end
|
|
847
|
-
end
|
|
848
877
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
878
|
+
def apply_scoping?(options)
|
|
879
|
+
!(options && options[:unscoped]) &&
|
|
880
|
+
(self.class.default_scopes?(all_queries: true) || self.class.global_current_scope)
|
|
881
|
+
end
|
|
853
882
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
883
|
+
def _query_constraints_hash
|
|
884
|
+
if self.class.query_constraints_list.nil?
|
|
885
|
+
{ @primary_key => id_in_database }
|
|
886
|
+
else
|
|
887
|
+
self.class.query_constraints_list.index_with do |column_name|
|
|
888
|
+
attribute_in_database(column_name)
|
|
889
|
+
end
|
|
860
890
|
end
|
|
861
891
|
end
|
|
862
|
-
end
|
|
863
892
|
|
|
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
|
|
893
|
+
# A hook to be overridden by association modules.
|
|
894
|
+
def destroy_associations
|
|
895
|
+
end
|
|
875
896
|
|
|
876
|
-
|
|
877
|
-
|
|
897
|
+
def destroy_row
|
|
898
|
+
_delete_row
|
|
899
|
+
end
|
|
878
900
|
|
|
879
|
-
|
|
880
|
-
|
|
901
|
+
def _delete_row
|
|
902
|
+
self.class._delete_record(_query_constraints_hash)
|
|
881
903
|
end
|
|
882
904
|
|
|
883
|
-
|
|
884
|
-
|
|
905
|
+
def _touch_row(attribute_names, time)
|
|
906
|
+
time ||= current_time_from_proper_timezone
|
|
885
907
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
_query_constraints_hash
|
|
890
|
-
)
|
|
891
|
-
end
|
|
908
|
+
attribute_names.each do |attr_name|
|
|
909
|
+
_write_attribute(attr_name, time)
|
|
910
|
+
end
|
|
892
911
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
return false if destroyed?
|
|
896
|
-
result = new_record? ? _create_record(&block) : _update_record(&block)
|
|
897
|
-
result != false
|
|
898
|
-
end
|
|
912
|
+
_update_row(attribute_names, "touch")
|
|
913
|
+
end
|
|
899
914
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
915
|
+
def _update_row(attribute_names, attempted_action = "update")
|
|
916
|
+
self.class._update_record(
|
|
917
|
+
attributes_with_values(attribute_names),
|
|
918
|
+
_query_constraints_hash
|
|
919
|
+
)
|
|
920
|
+
end
|
|
904
921
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
@_trigger_update_callback = affected_rows == 1
|
|
922
|
+
def create_or_update(**, &block)
|
|
923
|
+
_raise_readonly_record_error if readonly?
|
|
924
|
+
return false if destroyed?
|
|
925
|
+
result = new_record? ? _create_record(&block) : _update_record(&block)
|
|
926
|
+
result != false
|
|
911
927
|
end
|
|
912
928
|
|
|
913
|
-
|
|
929
|
+
# Updates the associated record with values matching those of the instance attributes.
|
|
930
|
+
# Returns the number of affected rows.
|
|
931
|
+
def _update_record(attribute_names = self.attribute_names)
|
|
932
|
+
attribute_names = attributes_for_update(attribute_names)
|
|
933
|
+
|
|
934
|
+
if attribute_names.empty?
|
|
935
|
+
affected_rows = 0
|
|
936
|
+
@_trigger_update_callback = true
|
|
937
|
+
else
|
|
938
|
+
affected_rows = _update_row(attribute_names)
|
|
939
|
+
@_trigger_update_callback = affected_rows == 1
|
|
940
|
+
end
|
|
914
941
|
|
|
915
|
-
|
|
942
|
+
@previously_new_record = false
|
|
916
943
|
|
|
917
|
-
|
|
918
|
-
end
|
|
944
|
+
yield(self) if block_given?
|
|
919
945
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
def _create_record(attribute_names = self.attribute_names)
|
|
923
|
-
attribute_names = attributes_for_create(attribute_names)
|
|
946
|
+
affected_rows
|
|
947
|
+
end
|
|
924
948
|
|
|
925
|
-
|
|
926
|
-
|
|
949
|
+
# Creates a record with values matching those of the instance attributes
|
|
950
|
+
# and returns its id.
|
|
951
|
+
def _create_record(attribute_names = self.attribute_names)
|
|
952
|
+
attribute_names = attributes_for_create(attribute_names)
|
|
927
953
|
|
|
928
|
-
|
|
929
|
-
connection
|
|
930
|
-
attributes_with_values(attribute_names),
|
|
931
|
-
returning_columns
|
|
932
|
-
)
|
|
954
|
+
self.class.with_connection do |connection|
|
|
955
|
+
returning_columns = self.class._returning_columns_for_insert(connection)
|
|
933
956
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
957
|
+
returning_values = self.class._insert_record(
|
|
958
|
+
connection,
|
|
959
|
+
attributes_with_values(attribute_names),
|
|
960
|
+
returning_columns
|
|
961
|
+
)
|
|
938
962
|
|
|
939
|
-
|
|
940
|
-
|
|
963
|
+
returning_columns.zip(returning_values).each do |column, value|
|
|
964
|
+
_write_attribute(column, type_for_attribute(column).deserialize(value)) if !_read_attribute(column)
|
|
965
|
+
end if returning_values
|
|
966
|
+
end
|
|
941
967
|
|
|
942
|
-
|
|
968
|
+
@new_record = false
|
|
969
|
+
@previously_new_record = true
|
|
943
970
|
|
|
944
|
-
|
|
945
|
-
end
|
|
971
|
+
yield(self) if block_given?
|
|
946
972
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
end
|
|
973
|
+
id
|
|
974
|
+
end
|
|
950
975
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
|
976
|
+
def verify_readonly_attribute(name)
|
|
977
|
+
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
|
|
978
|
+
end
|
|
958
979
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
980
|
+
def _raise_record_not_destroyed
|
|
981
|
+
@_association_destroy_exception ||= nil
|
|
982
|
+
key = self.class.primary_key
|
|
983
|
+
raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
|
|
984
|
+
ensure
|
|
985
|
+
@_association_destroy_exception = nil
|
|
986
|
+
end
|
|
962
987
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
988
|
+
def _raise_readonly_record_error
|
|
989
|
+
raise ReadOnlyRecord, "#{self.class} is marked as readonly"
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
def _raise_record_not_touched_error
|
|
993
|
+
raise ActiveRecordError, <<~MSG.squish
|
|
994
|
+
Cannot touch on a new or destroyed record object. Consider using
|
|
995
|
+
persisted?, new_record?, or destroyed? before touching.
|
|
996
|
+
MSG
|
|
997
|
+
end
|
|
969
998
|
end
|
|
970
999
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# = Active Record Query Cache
|
|
5
5
|
class QueryCache
|
|
6
|
+
# ActiveRecord::Base extends this module, so these methods are available in models.
|
|
6
7
|
module ClassMethods
|
|
7
8
|
# Enable the query cache within the block if Active Record is configured.
|
|
8
9
|
# If it's not, it will execute the given block.
|
|
@@ -20,11 +21,15 @@ module ActiveRecord
|
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
24
|
+
# Runs the block with the query cache disabled.
|
|
25
|
+
#
|
|
26
|
+
# If the query cache was enabled before the block was executed, it is
|
|
27
|
+
# enabled again after it.
|
|
25
28
|
#
|
|
26
|
-
# Set <tt>dirties: false</tt> to prevent query caches on all connections
|
|
27
|
-
# (By default, write operations
|
|
29
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections
|
|
30
|
+
# from being cleared by write operations. (By default, write operations
|
|
31
|
+
# dirty all connections' query caches in case they are replicas whose
|
|
32
|
+
# cache would now be outdated.)
|
|
28
33
|
def uncached(dirties: true, &block)
|
|
29
34
|
if connected? || !configurations.empty?
|
|
30
35
|
connection_pool.disable_query_cache(dirties: dirties, &block)
|
|
@@ -34,22 +39,24 @@ module ActiveRecord
|
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
module ExecutorHooks # :nodoc:
|
|
43
|
+
def self.run
|
|
44
|
+
ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each do |pool|
|
|
45
|
+
next if pool.db_config&.query_cache == false
|
|
46
|
+
pool.enable_query_cache!
|
|
47
|
+
end
|
|
41
48
|
end
|
|
42
|
-
end
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
def self.complete(pools)
|
|
51
|
+
pools.each do |pool|
|
|
52
|
+
pool.disable_query_cache!
|
|
53
|
+
pool.clear_query_cache
|
|
54
|
+
end
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
|
|
51
|
-
def self.install_executor_hooks(executor = ActiveSupport::Executor)
|
|
52
|
-
executor.register_hook(
|
|
58
|
+
def self.install_executor_hooks(executor = ActiveSupport::Executor) # :nodoc:
|
|
59
|
+
executor.register_hook(ExecutorHooks)
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
62
|
end
|
|
@@ -69,21 +69,77 @@ module ActiveRecord
|
|
|
69
69
|
#
|
|
70
70
|
# Tag comments can be prepended to the query:
|
|
71
71
|
#
|
|
72
|
-
#
|
|
72
|
+
# config.active_record.query_log_tags_prepend_comment = true
|
|
73
73
|
#
|
|
74
74
|
# For applications where the content will not change during the lifetime of
|
|
75
75
|
# the request or job execution, the tags can be cached for reuse in every query:
|
|
76
76
|
#
|
|
77
77
|
# config.active_record.cache_query_log_tags = true
|
|
78
78
|
module QueryLogs
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
class GetKeyHandler # :nodoc:
|
|
80
|
+
def initialize(name)
|
|
81
|
+
@name = name
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def call(context)
|
|
85
|
+
context[@name]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class IdentityHandler # :nodoc:
|
|
90
|
+
def initialize(value)
|
|
91
|
+
@value = value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def call(_context)
|
|
95
|
+
@value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class ZeroArityHandler # :nodoc:
|
|
100
|
+
def initialize(proc)
|
|
101
|
+
@proc = proc
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def call(_context)
|
|
105
|
+
@proc.call
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@taggings = {}.freeze
|
|
110
|
+
@tags = [ :application ].freeze
|
|
111
|
+
@prepend_comment = false
|
|
112
|
+
@cache_query_log_tags = false
|
|
113
|
+
@tags_formatter = false
|
|
114
|
+
|
|
84
115
|
thread_mattr_accessor :cached_comment, instance_accessor: false
|
|
85
116
|
|
|
86
117
|
class << self
|
|
118
|
+
attr_reader :tags, :taggings, :tags_formatter # :nodoc:
|
|
119
|
+
attr_accessor :prepend_comment, :cache_query_log_tags # :nodoc:
|
|
120
|
+
|
|
121
|
+
def taggings=(taggings) # :nodoc:
|
|
122
|
+
@taggings = taggings.freeze
|
|
123
|
+
@handlers = rebuild_handlers
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def tags=(tags) # :nodoc:
|
|
127
|
+
@tags = tags.freeze
|
|
128
|
+
@handlers = rebuild_handlers
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def tags_formatter=(format) # :nodoc:
|
|
132
|
+
@formatter = case format
|
|
133
|
+
when :legacy
|
|
134
|
+
LegacyFormatter
|
|
135
|
+
when :sqlcommenter
|
|
136
|
+
SQLCommenter
|
|
137
|
+
else
|
|
138
|
+
raise ArgumentError, "Formatter is unsupported: #{format}"
|
|
139
|
+
end
|
|
140
|
+
@tags_formatter = format
|
|
141
|
+
end
|
|
142
|
+
|
|
87
143
|
def call(sql, connection) # :nodoc:
|
|
88
144
|
comment = self.comment(connection)
|
|
89
145
|
|
|
@@ -100,36 +156,42 @@ module ActiveRecord
|
|
|
100
156
|
self.cached_comment = nil
|
|
101
157
|
end
|
|
102
158
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
self.tags_formatter =
|
|
106
|
-
case format
|
|
107
|
-
when :legacy
|
|
108
|
-
LegacyFormatter.new
|
|
109
|
-
when :sqlcommenter
|
|
110
|
-
SQLCommenter.new
|
|
111
|
-
else
|
|
112
|
-
raise ArgumentError, "Formatter is unsupported: #{formatter}"
|
|
113
|
-
end
|
|
159
|
+
def query_source_location # :nodoc:
|
|
160
|
+
LogSubscriber.backtrace_cleaner.first_clean_frame
|
|
114
161
|
end
|
|
115
162
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
163
|
+
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
def rebuild_handlers
|
|
167
|
+
handlers = []
|
|
168
|
+
@tags.each do |i|
|
|
169
|
+
if i.is_a?(Hash)
|
|
170
|
+
i.each do |k, v|
|
|
171
|
+
handlers << [k, build_handler(k, v)]
|
|
172
|
+
end
|
|
173
|
+
else
|
|
174
|
+
handlers << [i, build_handler(i)]
|
|
175
|
+
end
|
|
121
176
|
end
|
|
122
|
-
|
|
123
|
-
end
|
|
124
|
-
else
|
|
125
|
-
def query_source_location # :nodoc:
|
|
126
|
-
LogSubscriber.backtrace_cleaner.clean(caller_locations(1).each).first
|
|
177
|
+
handlers.sort_by! { |(key, _)| key.to_s }
|
|
127
178
|
end
|
|
128
|
-
end
|
|
129
179
|
|
|
130
|
-
|
|
180
|
+
def build_handler(name, handler = nil)
|
|
181
|
+
handler ||= @taggings[name]
|
|
182
|
+
if handler.nil?
|
|
183
|
+
GetKeyHandler.new(name)
|
|
184
|
+
elsif handler.respond_to?(:call)
|
|
185
|
+
if handler.arity == 0
|
|
186
|
+
ZeroArityHandler.new(handler)
|
|
187
|
+
else
|
|
188
|
+
handler
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
IdentityHandler.new(handler)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
131
194
|
|
|
132
|
-
private
|
|
133
195
|
# Returns an SQL comment +String+ containing the query log tags.
|
|
134
196
|
# Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
|
|
135
197
|
def comment(connection)
|
|
@@ -140,10 +202,6 @@ module ActiveRecord
|
|
|
140
202
|
end
|
|
141
203
|
end
|
|
142
204
|
|
|
143
|
-
def formatter
|
|
144
|
-
self.tags_formatter || self.update_formatter(:legacy)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
205
|
def uncached_comment(connection)
|
|
148
206
|
content = tag_content(connection)
|
|
149
207
|
|
|
@@ -153,7 +211,7 @@ module ActiveRecord
|
|
|
153
211
|
end
|
|
154
212
|
|
|
155
213
|
def escape_sql_comment(content)
|
|
156
|
-
# Sanitize a string to appear within
|
|
214
|
+
# Sanitize a string to appear within an SQL comment
|
|
157
215
|
# For compatibility, this also surrounding "/*+", "/*", and "*/"
|
|
158
216
|
# characters, possibly with single surrounding space.
|
|
159
217
|
# Then follows that by replacing any internal "*/" or "/ *" with
|
|
@@ -169,25 +227,15 @@ module ActiveRecord
|
|
|
169
227
|
context = ActiveSupport::ExecutionContext.to_h
|
|
170
228
|
context[:connection] ||= connection
|
|
171
229
|
|
|
172
|
-
pairs =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
val = if handler.nil?
|
|
177
|
-
context[key]
|
|
178
|
-
elsif handler.respond_to?(:call)
|
|
179
|
-
if handler.arity == 0
|
|
180
|
-
handler.call
|
|
181
|
-
else
|
|
182
|
-
handler.call(context)
|
|
183
|
-
end
|
|
184
|
-
else
|
|
185
|
-
handler
|
|
186
|
-
end
|
|
187
|
-
[key, val] unless val.nil?
|
|
230
|
+
pairs = @handlers.filter_map do |(key, handler)|
|
|
231
|
+
val = handler.call(context)
|
|
232
|
+
@formatter.format(key, val) unless val.nil?
|
|
188
233
|
end
|
|
189
|
-
|
|
234
|
+
@formatter.join(pairs)
|
|
190
235
|
end
|
|
191
236
|
end
|
|
237
|
+
|
|
238
|
+
@handlers = rebuild_handlers
|
|
239
|
+
self.tags_formatter = :legacy
|
|
192
240
|
end
|
|
193
241
|
end
|