1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
|
# frozen_string_literal: true
require "active_support/core_ext/enumerable"
require "active_support/core_ext/module/delegation"
require "active_support/parameter_filter"
require "concurrent/map"
module ActiveRecord
# = Active Record \Core
module Core
extend ActiveSupport::Concern
include ActiveModel::Access
included do
##
# :singleton-method:
#
# Accepts a logger conforming to the interface of Log4r or the default
# Ruby +Logger+ class, which is then passed on to any new database
# connections made. You can retrieve this logger by calling +logger+ on
# either an Active Record model class or an Active Record model instance.
class_attribute :logger, instance_writer: false
class_attribute :_destroy_association_async_job, instance_accessor: false, default: "ActiveRecord::DestroyAssociationAsyncJob"
# The job class used to destroy associations in the background.
def self.destroy_association_async_job
if _destroy_association_async_job.is_a?(String)
self._destroy_association_async_job = _destroy_association_async_job.constantize
end
_destroy_association_async_job
rescue NameError => error
raise NameError, "Unable to load destroy_association_async_job: #{error.message}"
end
singleton_class.alias_method :destroy_association_async_job=, :_destroy_association_async_job=
delegate :destroy_association_async_job, to: :class
##
# :singleton-method:
#
# Specifies the maximum number of records that will be destroyed in a
# single background job by the <tt>dependent: :destroy_async</tt>
# association option. When +nil+ (default), all dependent records will be
# destroyed in a single background job. If specified, the records to be
# destroyed will be split into multiple background jobs.
class_attribute :destroy_association_async_batch_size, instance_writer: false, instance_predicate: false, default: nil
##
# Contains the database configuration - as is typically stored in config/database.yml -
# as an ActiveRecord::DatabaseConfigurations object.
#
# For example, the following database.yml...
#
# development:
# adapter: sqlite3
# database: storage/development.sqlite3
#
# production:
# adapter: sqlite3
# database: storage/production.sqlite3
#
# ...would result in ActiveRecord::Base.configurations to look like this:
#
# #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
# @name="primary", @config={adapter: "sqlite3", database: "storage/development.sqlite3"}>,
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
# @name="primary", @config={adapter: "sqlite3", database: "storage/production.sqlite3"}>
# ]>
def self.configurations=(config)
@@configurations = ActiveRecord::DatabaseConfigurations.new(config)
end
self.configurations = {}
# Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
def self.configurations
@@configurations
end
##
# :singleton-method:
# Force enumeration of all columns in SELECT statements.
# e.g. <tt>SELECT first_name, last_name FROM ...</tt> instead of <tt>SELECT * FROM ...</tt>
# This avoids +PreparedStatementCacheExpired+ errors when a column is added
# to the database while the app is running.
class_attribute :enumerate_columns_in_select_statements, instance_accessor: false, default: false
class_attribute :belongs_to_required_by_default, instance_accessor: false
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
class_attribute :has_many_inversing, instance_accessor: false, default: false
class_attribute :run_commit_callbacks_on_first_saved_instances_in_transaction, instance_accessor: false, default: true
class_attribute :default_connection_handler, instance_writer: false
class_attribute :default_role, instance_writer: false
class_attribute :default_shard, instance_writer: false
class_attribute :shard_selector, instance_accessor: false, default: nil
##
# :singleton-method:
#
# Specifies the attributes that will be included in the output of the
# #inspect method:
#
# Post.attributes_for_inspect = [:id, :title]
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
#
# When set to `:all` inspect will list all the record's attributes:
#
# Post.attributes_for_inspect = :all
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
class_attribute :attributes_for_inspect, instance_accessor: false, default: :all
def self.application_record_class? # :nodoc:
if ActiveRecord.application_record_class
self == ActiveRecord.application_record_class
else
if defined?(ApplicationRecord) && self == ApplicationRecord
true
end
end
end
self.filter_attributes = []
def self.connection_handler
ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] || default_connection_handler
end
def self.connection_handler=(handler)
ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
end
def self.asynchronous_queries_session # :nodoc:
asynchronous_queries_tracker.current_session
end
def self.asynchronous_queries_tracker # :nodoc:
ActiveSupport::IsolatedExecutionState[:active_record_asynchronous_queries_tracker] ||= \
AsynchronousQueriesTracker.new
end
# Returns the symbol representing the current connected role.
#
# ActiveRecord::Base.connected_to(role: :writing) do
# ActiveRecord::Base.current_role #=> :writing
# end
#
# ActiveRecord::Base.connected_to(role: :reading) do
# ActiveRecord::Base.current_role #=> :reading
# end
def self.current_role
connected_to_stack.reverse_each do |hash|
return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
end
default_role
end
# Returns the symbol representing the current connected shard.
#
# ActiveRecord::Base.connected_to(role: :reading) do
# ActiveRecord::Base.current_shard #=> :default
# end
#
# ActiveRecord::Base.connected_to(role: :writing, shard: :one) do
# ActiveRecord::Base.current_shard #=> :one
# end
def self.current_shard
connected_to_stack.reverse_each do |hash|
return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self)
end
default_shard
end
# Returns the symbol representing the current setting for
# preventing writes.
#
# ActiveRecord::Base.connected_to(role: :reading) do
# ActiveRecord::Base.current_preventing_writes #=> true
# end
#
# ActiveRecord::Base.connected_to(role: :writing) do
# ActiveRecord::Base.current_preventing_writes #=> false
# end
def self.current_preventing_writes
connected_to_stack.reverse_each do |hash|
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
end
false
end
def self.connected_to_stack # :nodoc:
if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
connected_to_stack
else
connected_to_stack = Concurrent::Array.new
ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] = connected_to_stack
connected_to_stack
end
end
def self.connection_class=(b) # :nodoc:
@connection_class = b
end
def self.connection_class # :nodoc:
@connection_class ||= false
end
def self.connection_class? # :nodoc:
self.connection_class
end
def self.connection_class_for_self # :nodoc:
klass = self
until klass == Base
break if klass.connection_class?
klass = klass.superclass
end
klass
end
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
self.default_role = ActiveRecord.writing_role
self.default_shard = :default
def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
case ActiveRecord.action_on_strict_loading_violation
when :raise
message = reflection.strict_loading_violation_message(owner)
raise ActiveRecord::StrictLoadingViolationError.new(message)
when :log
name = "strict_loading_violation.active_record"
ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
end
end
end
module ClassMethods
def initialize_find_by_cache # :nodoc:
@find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
end
def find(*ids) # :nodoc:
# We don't have cache keys for this stuff yet
return super unless ids.length == 1
return super if block_given? || primary_key.nil? || scope_attributes?
id = ids.first
return super if StatementCache.unsupported_value?(id)
cached_find_by([primary_key], [id]) ||
raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
end
def find_by(*args) # :nodoc:
return super if scope_attributes?
hash = args.first
return super unless Hash === hash
hash = hash.each_with_object({}) do |(key, value), h|
key = key.to_s
key = attribute_aliases[key] || key
return super if reflect_on_aggregation(key)
reflection = _reflect_on_association(key)
if !reflection
value = value.id if value.respond_to?(:id)
elsif reflection.belongs_to? && !reflection.polymorphic?
key = reflection.join_foreign_key
pkey = reflection.join_primary_key
if pkey.is_a?(Array)
if pkey.all? { |attribute| value.respond_to?(attribute) }
value = pkey.map do |attribute|
if attribute == "id"
value.id_value
else
value.public_send(attribute)
end
end
composite_primary_key = true
end
else
value = value.public_send(pkey) if value.respond_to?(pkey)
end
end
if !composite_primary_key &&
(!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
return super
end
h[key] = value
end
cached_find_by(hash.keys, hash.values)
end
def find_by!(*args) # :nodoc:
find_by(*args) || where(*args).raise_record_not_found_exception!
end
def initialize_generated_modules # :nodoc:
generated_association_methods
end
def generated_association_methods # :nodoc:
@generated_association_methods ||= begin
mod = const_set(:GeneratedAssociationMethods, Module.new)
private_constant :GeneratedAssociationMethods
include mod
mod
end
end
# Returns columns which shouldn't be exposed while calling +#inspect+.
def filter_attributes
if @filter_attributes.nil?
superclass.filter_attributes
else
@filter_attributes
end
end
# Specifies columns which shouldn't be exposed while calling +#inspect+.
def filter_attributes=(filter_attributes)
@inspection_filter = nil
@filter_attributes = filter_attributes
end
def inspection_filter # :nodoc:
if @filter_attributes.nil?
superclass.inspection_filter
else
@inspection_filter ||= begin
mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask)
end
end
end
# Returns a string like 'Post(id:integer, title:string, body:text)'
def inspect # :nodoc:
if self == Base || singleton_class?
super
elsif abstract_class?
"#{super}(abstract)"
elsif !schema_loaded? && !connected?
"#{super} (call '#{super}.load_schema' to load schema informations)"
elsif table_exists?
attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
"#{super}(#{attr_list})"
else
"#{super}(Table doesn't exist)"
end
end
# Returns an instance of +Arel::Table+ loaded with the current table name.
def arel_table # :nodoc:
@arel_table ||= Arel::Table.new(table_name, klass: self)
end
def predicate_builder # :nodoc:
@predicate_builder ||= PredicateBuilder.new(table_metadata)
end
def type_caster # :nodoc:
TypeCaster::Map.new(self)
end
def cached_find_by_statement(connection, key, &block) # :nodoc:
cache = @find_by_statement_cache[connection.prepared_statements]
cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
end
private
def inherited(subclass)
super
# initialize cache at class definition for thread safety
subclass.initialize_find_by_cache
unless subclass.base_class?
klass = self
until klass.base_class?
klass.initialize_find_by_cache
klass = klass.superclass
end
end
subclass.class_eval do
@arel_table = nil
@predicate_builder = nil
@inspection_filter = nil
@filter_attributes ||= nil
@generated_association_methods ||= nil
end
end
def relation
relation = Relation.create(self)
if finder_needs_type_condition? && !ignore_default_scope?
relation.where!(type_condition)
else
relation
end
end
def table_metadata
TableMetadata.new(self, arel_table)
end
def cached_find_by(keys, values)
with_connection do |connection|
statement = cached_find_by_statement(connection, keys) { |params|
wheres = keys.index_with do |key|
if key.is_a?(Array)
[key.map { params.bind }]
else
params.bind
end
end
where(wheres).limit(1)
}
begin
statement.execute(values.flatten, connection, allow_retry: true).first
rescue TypeError
raise ActiveRecord::StatementInvalid
end
end
end
end
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
# In both instances, valid attribute keys are determined by the column names of the associated table --
# hence you can't have attributes that aren't part of the table columns.
#
# ==== Example
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil)
@new_record = true
@attributes = self.class._default_attributes.deep_dup
init_internals
initialize_internals_callback
super
yield self if block_given?
_run_initialize_callbacks
end
# Initialize an empty model object from +coder+. +coder+ should be
# the result of previously encoding an Active Record model, using
# #encode_with.
#
# class Post < ActiveRecord::Base
# end
#
# old_post = Post.new(title: "hello world")
# coder = {}
# old_post.encode_with(coder)
#
# post = Post.allocate
# post.init_with(coder)
# post.title # => 'hello world'
def init_with(coder, &block)
coder = LegacyYamlAdapter.convert(coder)
attributes = self.class.yaml_encoder.decode(coder)
init_with_attributes(attributes, coder["new_record"], &block)
end
##
# Initialize an empty model object from +attributes+.
# +attributes+ should be an attributes object, and unlike the
# `initialize` method, no assignment calls are made per attribute.
def init_with_attributes(attributes, new_record = false) # :nodoc:
@new_record = new_record
@attributes = attributes
init_internals
yield self if block_given?
_run_find_callbacks
_run_initialize_callbacks
self
end
##
# :method: clone
# Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
# That means that modifying attributes of the clone will modify the original, since they will both point to the
# same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
#
# user = User.first
# new_user = user.clone
# user.name # => "Bob"
# new_user.name = "Joe"
# user.name # => "Joe"
#
# user.object_id == new_user.object_id # => false
# user.name.object_id == new_user.name.object_id # => true
#
# user.name.object_id == user.dup.name.object_id # => false
##
# :method: dup
# Duped objects have no id assigned and are treated as new records. Note
# that this is a "shallow" copy as it copies the object's attributes
# only, not its associations. The extent of a "deep" copy is application
# specific and is therefore left to the application to implement according
# to its need.
# The dup method does not preserve the timestamps (created|updated)_(at|on)
# and locking column.
##
def initialize_dup(other) # :nodoc:
@attributes = @attributes.deep_dup
if self.class.composite_primary_key?
@primary_key.each { |key| @attributes.reset(key) }
else
@attributes.reset(@primary_key)
end
_run_initialize_callbacks
@new_record = true
@previously_new_record = false
@destroyed = false
@_start_transaction_state = nil
super
end
# Populate +coder+ with attributes about this record that should be
# serialized. The structure of +coder+ defined in this method is
# guaranteed to match the structure of +coder+ passed to the #init_with
# method.
#
# Example:
#
# class Post < ActiveRecord::Base
# end
# coder = {}
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
self.class.yaml_encoder.encode(@attributes, coder)
coder["new_record"] = new_record?
coder["active_record_yaml_version"] = 2
end
##
# :method: slice
#
# :call-seq: slice(*methods)
#
# Returns a hash of the given methods with their names as keys and returned
# values as values.
#
# topic = Topic.new(title: "Budget", author_name: "Jason")
# topic.slice(:title, :author_name)
# => { "title" => "Budget", "author_name" => "Jason" }
#
#--
# Implemented by ActiveModel::Access#slice.
##
# :method: values_at
#
# :call-seq: values_at(*methods)
#
# Returns an array of the values returned by the given methods.
#
# topic = Topic.new(title: "Budget", author_name: "Jason")
# topic.values_at(:title, :author_name)
# => ["Budget", "Jason"]
#
#--
# Implemented by ActiveModel::Access#values_at.
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
#
# Note that new records are different from any other record by definition, unless the
# other record is the receiver itself. Besides, if you fetch existing records with
# +select+ and leave the ID out, you're on your own, this predicate will return false.
#
# Note also that destroying a record preserves its ID in the model instance, so deleted
# models are still comparable.
def ==(comparison_object)
super ||
comparison_object.instance_of?(self.class) &&
primary_key_values_present? &&
comparison_object.id == id
end
alias :eql? :==
# Delegates to id in order to allow two records of the same type and id to work with something like:
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
id = self.id
if primary_key_values_present?
self.class.hash ^ id.hash
else
super
end
end
# Clone and freeze the attributes hash such that associations are still
# accessible, even on destroyed records, but cloned models will not be
# frozen.
def freeze
@attributes = @attributes.clone.freeze
self
end
# Returns +true+ if the attributes hash has been frozen.
def frozen?
@attributes.frozen?
end
# Allows sort on objects
def <=>(other_object)
if other_object.is_a?(self.class)
to_key <=> other_object.to_key
else
super
end
end
def present? # :nodoc:
true
end
def blank? # :nodoc:
false
end
# Returns +true+ if the record is read only.
def readonly?
@readonly
end
# Returns +true+ if the record is in strict_loading mode.
def strict_loading?
@strict_loading
end
# Sets the record to strict_loading mode. This will raise an error
# if the record tries to lazily load an association.
#
# user = User.first
# user.strict_loading! # => true
# user.address.city
# => ActiveRecord::StrictLoadingViolationError
# user.comments.to_a
# => ActiveRecord::StrictLoadingViolationError
#
# ==== Parameters
#
# * +value+ - Boolean specifying whether to enable or disable strict loading.
# * <tt>:mode</tt> - Symbol specifying strict loading mode. Defaults to :all. Using
# :n_plus_one_only mode will only raise an error if an association that
# will lead to an n plus one query is lazily loaded.
#
# ==== Examples
#
# user = User.first
# user.strict_loading!(false) # => false
# user.address.city # => "Tatooine"
# user.comments.to_a # => [#<Comment:0x00...]
#
# user.strict_loading!(mode: :n_plus_one_only)
# user.address.city # => "Tatooine"
# user.comments.to_a # => [#<Comment:0x00...]
# user.comments.first.ratings.to_a
# => ActiveRecord::StrictLoadingViolationError
def strict_loading!(value = true, mode: :all)
unless [:all, :n_plus_one_only].include?(mode)
raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
end
@strict_loading_mode = mode
@strict_loading = value
end
attr_reader :strict_loading_mode
# Returns +true+ if the record uses strict_loading with +:n_plus_one_only+ mode enabled.
def strict_loading_n_plus_one_only?
@strict_loading_mode == :n_plus_one_only
end
# Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
def strict_loading_all?
@strict_loading_mode == :all
end
# Marks this record as read only.
#
# customer = Customer.first
# customer.readonly!
# customer.save # Raises an ActiveRecord::ReadOnlyRecord
def readonly!
@readonly = true
end
def connection_handler
self.class.connection_handler
end
# Returns the attributes of the record as a nicely formatted string.
#
# Post.first.inspect
# #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
#
# The attributes can be limited by setting <tt>.attributes_for_inspect</tt>.
#
# Post.attributes_for_inspect = [:id, :title]
# Post.first.inspect
# #=> "#<Post id: 1, title: "Hello, World!">"
def inspect
inspect_with_attributes(attributes_for_inspect)
end
# Returns all attributes of the record as a nicely formatted string,
# ignoring <tt>.attributes_for_inspect</tt>.
#
# Post.first.full_inspect
# #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
#
def full_inspect
inspect_with_attributes(all_attributes_for_inspect)
end
# Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
# when pp is required.
def pretty_print(pp)
return super if custom_inspect_method_defined?
pp.object_address_group(self) do
if @attributes
attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
attr_name = attr_name.to_s
pp.breakable " "
pp.group(1) do
pp.text attr_name
pp.text ":"
pp.breakable
value = attribute_for_inspect(attr_name)
pp.text value
end
end
else
pp.breakable " "
pp.text "not initialized"
end
end
end
private
# +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
# the array, and then rescues from the possible +NoMethodError+. If those elements are
# +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
# which significantly impacts upon performance.
#
# So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here.
#
# See also https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
def to_ary
nil
end
def init_internals
@readonly = false
@previously_new_record = false
@destroyed = false
@marked_for_destruction = false
@destroyed_by_association = nil
@_start_transaction_state = nil
klass = self.class
@primary_key = klass.primary_key
@strict_loading = klass.strict_loading_by_default
@strict_loading_mode = :all
klass.define_attribute_methods
end
def initialize_internals_callback
end
def custom_inspect_method_defined?
self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
end
class InspectionMask < DelegateClass(::String)
def pretty_print(pp)
pp.text __getobj__
end
end
private_constant :InspectionMask
def inspection_filter
self.class.inspection_filter
end
def inspect_with_attributes(attributes_to_list)
inspection = if @attributes
attributes_to_list.filter_map do |name|
name = name.to_s
if _has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
end
end.join(", ")
else
"not initialized"
end
"#<#{self.class} #{inspection}>"
end
def attributes_for_inspect
self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
end
def all_attributes_for_inspect
return [] unless @attributes
attribute_names
end
end
end
|