core.rb 20.4 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/string/filters"
5
require "active_support/parameter_filter"
6
require "concurrent/map"
7 8

module ActiveRecord
J
Jon Leighton 已提交
9 10
  module Core
    extend ActiveSupport::Concern
11

J
Jon Leighton 已提交
12
    included do
J
Jon Leighton 已提交
13 14
      ##
      # :singleton-method:
J
Jon Leighton 已提交
15 16 17 18 19
      #
      # Accepts a logger conforming to the interface of Log4r which is then
      # passed on to any new database connections made and which can be
      # retrieved on both a class and instance level by calling +logger+.
      mattr_accessor :logger, instance_writer: false
20

O
Olivier Lacan 已提交
21 22 23 24 25 26 27
      ##
      # :singleton-method:
      #
      # Specifies if the methods calling database queries should be logged below
      # their relevant queries. Defaults to false.
      mattr_accessor :verbose_query_logs, instance_writer: false, default: false

J
Jon Leighton 已提交
28 29
      ##
      # Contains the database configuration - as is typically stored in config/database.yml -
30
      # as an ActiveRecord::DatabaseConfigurations object.
J
Jon Leighton 已提交
31 32 33 34 35 36 37 38 39 40 41 42 43
      #
      # For example, the following database.yml...
      #
      #   development:
      #     adapter: sqlite3
      #     database: db/development.sqlite3
      #
      #   production:
      #     adapter: sqlite3
      #     database: db/production.sqlite3
      #
      # ...would result in ActiveRecord::Base.configurations to look like this:
      #
44 45 46 47 48 49
      #   #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
      #     #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
      #       @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
      #     #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
      #       @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
      #   ]>
50
      def self.configurations=(config)
51
        @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
52
      end
J
Jon Leighton 已提交
53 54
      self.configurations = {}

55
      # Returns fully resolved ActiveRecord::DatabaseConfigurations object
56 57 58 59
      def self.configurations
        @@configurations
      end

J
Jon Leighton 已提交
60 61 62 63
      ##
      # :singleton-method:
      # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
      # dates and times from the database. This is set to :utc by default.
64
      mattr_accessor :default_timezone, instance_writer: false, default: :utc
J
Jon Leighton 已提交
65 66 67 68 69 70 71 72 73

      ##
      # :singleton-method:
      # Specifies the format to use when dumping the database schema with Rails'
      # Rakefile. If :sql, the schema is dumped as (potentially database-
      # specific) SQL statements. If :ruby, the schema is dumped as an
      # ActiveRecord::Schema file which can be loaded into any database that
      # supports migrations. Use :ruby if you want to have different database
      # adapters for, e.g., your development and test environments.
74
      mattr_accessor :schema_format, instance_writer: false, default: :ruby
J
Jon Leighton 已提交
75

76 77
      ##
      # :singleton-method:
78
      # Specifies if an error should be raised if the query has an order being
79
      # ignored when doing batch queries. Useful in applications where the
80
      # scope being ignored is error-worthy, rather than a warning.
81
      mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
82

83 84 85 86 87
      # :singleton-method:
      # Specify the behavior for unsafe raw query methods. Values are as follows
      #   deprecated - Warnings are logged when unsafe raw SQL is passed to
      #                query methods.
      #   disabled   - Unsafe raw SQL passed to query methods results in
B
Ben Toews 已提交
88 89
      #                UnknownAttributeReference exception.
      mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
90

J
Jon Leighton 已提交
91 92 93
      ##
      # :singleton-method:
      # Specify whether or not to use timestamps for migration versions
94
      mattr_accessor :timestamped_migrations, instance_writer: false, default: true
J
Jon Leighton 已提交
95

96 97 98
      ##
      # :singleton-method:
      # Specify whether schema dump should happen at the end of the
99
      # db:migrate rails command. This is true by default, which is useful for the
100 101
      # development environment. This should ideally be false in the production
      # environment where dumping schema is rarely needed.
102
      mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
103

104 105 106
      ##
      # :singleton-method:
      # Specifies which database schemas to dump when calling db:structure:dump.
107 108 109 110
      # If the value is :schema_search_path (the default), any schemas listed in
      # schema_search_path are dumped. Use :all to dump all schemas regardless
      # of schema_search_path, or a string of comma separated schemas for a
      # custom list.
111
      mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
112

113 114
      ##
      # :singleton-method:
115 116 117 118
      # Specify a threshold for the size of query result sets. If the number of
      # records in the set exceeds the threshold, a warning is logged. This can
      # be used to identify queries which load thousands of records and
      # potentially cause memory bloat.
119 120
      mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false

121 122
      mattr_accessor :maintain_test_schema, instance_accessor: false

123 124
      mattr_accessor :belongs_to_required_by_default, instance_accessor: false

125 126
      mattr_accessor :connection_handlers, instance_accessor: false, default: {}

127
      class_attribute :default_connection_handler, instance_writer: false
128

129
      self.filter_attributes = []
130

131
      def self.connection_handler
132
        Thread.current.thread_variable_get("ar_connection_handler") || default_connection_handler
133 134 135
      end

      def self.connection_handler=(handler)
136
        Thread.current.thread_variable_set("ar_connection_handler", handler)
137 138 139
      end

      self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
140
      self.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
141 142
    end

143
    module ClassMethods
144
      def initialize_find_by_cache # :nodoc:
145
        @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
146 147
      end

148
      def inherited(child_class) # :nodoc:
149
        # initialize cache at class definition for thread safety
150 151 152 153
        child_class.initialize_find_by_cache
        super
      end

154
      def find(*ids) # :nodoc:
155 156 157 158
        # We don't have cache keys for this stuff yet
        return super unless ids.length == 1
        return super if block_given? ||
                        primary_key.nil? ||
159
                        scope_attributes? ||
160
                        columns_hash.include?(inheritance_column)
161

162
        id = ids.first
163

164
        return super if StatementCache.unsupported_value?(id)
165 166 167

        key = primary_key

168 169
        statement = cached_find_by_statement(key) { |params|
          where(key => params.bind).limit(1)
170
        }
171

172
        record = statement.execute([id], connection).first
173
        unless record
174 175
          raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
                                   name, primary_key, id)
176 177
        end
        record
178
      rescue ::RangeError
179 180
        raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
                                 name, primary_key)
181 182
      end

183
      def find_by(*args) # :nodoc:
184
        return super if scope_attributes? || reflect_on_all_aggregations.any?
185 186 187

        hash = args.first

188
        return super if !(Hash === hash) || hash.values.any? { |v|
189
          StatementCache.unsupported_value?(v)
190
        }
191

192 193 194
        # We can't cache Post.find_by(author: david) ...yet
        return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }

195
        keys = hash.keys
196

197 198
        statement = cached_find_by_statement(keys) { |params|
          wheres = keys.each_with_object({}) { |param, o|
199
            o[param] = params.bind
200
          }
201
          where(wheres).limit(1)
202 203
        }
        begin
204
          statement.execute(hash.values, connection).first
205 206
        rescue TypeError
          raise ActiveRecord::StatementInvalid
207
        rescue ::RangeError
208
          nil
209 210 211
        end
      end

212
      def find_by!(*args) # :nodoc:
213
        find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name))
214 215
      end

216
      def initialize_generated_modules # :nodoc:
217
        generated_association_methods
218 219
      end

220
      def generated_association_methods # :nodoc:
221 222
        @generated_association_methods ||= begin
          mod = const_set(:GeneratedAssociationMethods, Module.new)
223
          private_constant :GeneratedAssociationMethods
224
          include mod
225

226 227 228 229
          mod
        end
      end

230
      # Returns columns which shouldn't be exposed while calling +#inspect+.
231 232 233 234 235 236 237 238
      def filter_attributes
        if defined?(@filter_attributes)
          @filter_attributes
        else
          superclass.filter_attributes
        end
      end

239
      # Specifies columns which shouldn't be exposed while calling +#inspect+.
240
      attr_writer :filter_attributes
241

242
      # Returns a string like 'Post(id:integer, title:string, body:text)'
243
      def inspect # :nodoc:
244 245 246 247
        if self == Base
          super
        elsif abstract_class?
          "#{super}(abstract)"
248
        elsif !connected?
A
Arun Agrawal 已提交
249
          "#{super} (call '#{super}.connection' to establish a connection)"
250
        elsif table_exists?
251
          attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
252 253 254 255 256 257
          "#{super}(#{attr_list})"
        else
          "#{super}(Table doesn't exist)"
        end
      end

258
      # Overwrite the default class equality method to provide support for decorated models.
259
      def ===(object) # :nodoc:
260 261 262
        object.is_a?(self)
      end

P
Peter Suschlik 已提交
263
      # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
O
Oscar Del Ben 已提交
264 265
      #
      #   class Post < ActiveRecord::Base
266
      #     scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
O
Oscar Del Ben 已提交
267
      #   end
268
      def arel_table # :nodoc:
269
        @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
270 271
      end

272
      def arel_attribute(name, table = arel_table) # :nodoc:
273 274 275 276
        name = attribute_alias(name) if attribute_alias?(name)
        table[name]
      end

277
      def predicate_builder # :nodoc:
278
        @predicate_builder ||= PredicateBuilder.new(table_metadata)
279 280
      end

281 282 283 284
      def type_caster # :nodoc:
        TypeCaster::Map.new(self)
      end

285 286
      private

A
Akira Matsuda 已提交
287
        def cached_find_by_statement(key, &block)
288
          cache = @find_by_statement_cache[connection.prepared_statements]
289
          cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
290
        end
291

A
Akira Matsuda 已提交
292
        def relation
293
          relation = Relation.create(self)
294

295
          if finder_needs_type_condition? && !ignore_default_scope?
296 297
            relation.where!(type_condition)
            relation.create_with!(inheritance_column.to_s => sti_name)
298 299 300
          else
            relation
          end
301
        end
302

A
Akira Matsuda 已提交
303
        def table_metadata
304 305
          TableMetadata.new(self, arel_table)
        end
306 307 308 309 310 311 312
    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.
    #
313
    # ==== Example:
314
    #   # Instantiates a single new object
A
AvnerCohen 已提交
315
    #   User.new(first_name: 'Jamie')
316
    def initialize(attributes = nil)
317
      self.class.define_attribute_methods
318
      @attributes = self.class._default_attributes.deep_dup
319 320

      init_internals
321
      initialize_internals_callback
322

323
      assign_attributes(attributes) if attributes
324 325

      yield self if block_given?
326
      _run_initialize_callbacks
327 328
    end

329 330
    # Initialize an empty model object from +coder+. +coder+ should be
    # the result of previously encoding an Active Record model, using
331
    # #encode_with.
332 333 334 335
    #
    #   class Post < ActiveRecord::Base
    #   end
    #
336 337 338 339
    #   old_post = Post.new(title: "hello world")
    #   coder = {}
    #   old_post.encode_with(coder)
    #
340
    #   post = Post.allocate
341
    #   post.init_with(coder)
342
    #   post.title # => 'hello world'
343
    def init_with(coder, &block)
344
      coder = LegacyYamlAdapter.convert(self.class, coder)
345 346
      attributes = self.class.yaml_encoder.decode(coder)
      init_with_attributes(attributes, coder["new_record"], &block)
347
    end
348

349
    ##
350 351
    # Initialize an empty model object from +attributes+.
    # +attributes+ should be an attributes object, and unlike the
352 353 354
    # `initialize` method, no assignment calls are made per attribute.
    #
    # :nodoc:
355
    def init_with_attributes(attributes, new_record = false)
356 357
      init_internals

358
      @new_record = new_record
359 360
      @attributes = attributes

361 362
      self.class.define_attribute_methods

363 364 365 366 367 368 369 370
      yield self if block_given?

      _run_find_callbacks
      _run_initialize_callbacks

      self
    end

371 372 373 374
    ##
    # :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
V
Vijay Dev 已提交
375
    # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
376 377 378 379 380 381 382 383 384
    #
    #   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
385
    #
386
    #   user.name.object_id == user.dup.name.object_id  # => false
387

388 389
    ##
    # :method: dup
390 391 392 393 394 395
    # 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).
396

397
    ##
398
    def initialize_dup(other) # :nodoc:
399
      @attributes = @attributes.deep_dup
400
      @attributes.reset(self.class.primary_key)
401

402
      _run_initialize_callbacks
403

404 405 406 407
      @new_record               = true
      @destroyed                = false
      @_start_transaction_state = {}
      @transaction_state        = nil
408 409 410 411 412 413

      super
    end

    # Populate +coder+ with attributes about this record that should be
    # serialized. The structure of +coder+ defined in this method is
414
    # guaranteed to match the structure of +coder+ passed to the #init_with
415 416 417 418 419 420 421 422
    # method.
    #
    # Example:
    #
    #   class Post < ActiveRecord::Base
    #   end
    #   coder = {}
    #   Post.new.encode_with(coder)
423
    #   coder # => {"attributes" => {"id" => nil, ... }}
424
    def encode_with(coder)
425
      self.class.yaml_encoder.encode(@attributes, coder)
426 427
      coder["new_record"] = new_record?
      coder["active_record_yaml_version"] = 2
428 429 430 431 432 433 434 435 436 437 438 439 440 441
    end

    # 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) &&
442
        !id.nil? &&
443 444 445 446 447 448 449
        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
450
      if id
451
        self.class.hash ^ id.hash
452 453 454
      else
        super
      end
455 456
    end

457 458 459
    # Clone and freeze the attributes hash such that associations are still
    # accessible, even on destroyed records, but cloned models will not be
    # frozen.
460
    def freeze
461
      @attributes = @attributes.clone.freeze
462
      self
463 464 465 466
    end

    # Returns +true+ if the attributes hash has been frozen.
    def frozen?
467
      @attributes.frozen?
468 469
    end

470 471 472
    # Allows sort on objects
    def <=>(other_object)
      if other_object.is_a?(self.class)
473
        to_key <=> other_object.to_key
474 475 476 477 478
      else
        super
      end
    end

479 480 481 482 483 484 485 486 487 488 489
    # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
    # attributes will be marked as read only since they cannot be saved.
    def readonly?
      @readonly
    end

    # Marks this record as read only.
    def readonly!
      @readonly = true
    end

490 491 492 493
    def connection_handler
      self.class.connection_handler
    end

494 495
    # Returns the contents of the record as a nicely formatted string.
    def inspect
496
      # We check defined?(@attributes) not to issue warnings if the object is
497
      # allocated but not initialized.
498
      inspection = if defined?(@attributes) && @attributes
499
        self.class.attribute_names.collect do |name|
500
          if has_attribute?(name)
501
            attr = _read_attribute(name)
502 503
            value = if attr.nil?
              attr.inspect
504
            else
505 506
              attr = format_for_inspect(attr)
              inspection_filter.filter_param(name, attr)
507
            end
508
            "#{name}: #{value}"
509
          end
510
        end.compact.join(", ")
511 512 513
      else
        "not initialized"
      end
514

515 516 517
      "#<#{self.class} #{inspection}>"
    end

518
    # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
519 520
    # when pp is required.
    def pretty_print(pp)
521
      return super if custom_inspect_method_defined?
522 523
      pp.object_address_group(self) do
        if defined?(@attributes) && @attributes
524 525
          attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
          pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
526
            pp.breakable " "
527
            pp.group(1) do
528
              pp.text attr_name
529
              pp.text ":"
530
              pp.breakable
531
              value = _read_attribute(attr_name)
532 533
              value = inspection_filter.filter_param(attr_name, value) unless value.nil?
              pp.pp value
534 535 536
            end
          end
        else
537 538
          pp.breakable " "
          pp.text "not initialized"
539 540 541 542
        end
      end
    end

543 544
    # Returns a hash of the given methods with their names as keys and returned values as values.
    def slice(*methods)
545
      Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
546 547
    end

548 549
    private

550 551 552 553 554 555 556
      # +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.
      #
557
      # See also https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
558
      def to_ary
559 560
        nil
      end
561

562 563 564 565 566 567 568 569 570
      def init_internals
        @readonly                 = false
        @destroyed                = false
        @marked_for_destruction   = false
        @destroyed_by_association = nil
        @new_record               = true
        @_start_transaction_state = {}
        @transaction_state        = nil
      end
571

572 573
      def initialize_internals_callback
      end
574

575 576 577 578
      def thaw
        if frozen?
          @attributes = @attributes.dup
        end
579
      end
580

581 582 583
      def custom_inspect_method_defined?
        self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
      end
584

585 586 587 588 589 590 591 592
      def inspection_filter
        @inspection_filter ||= begin
          mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
          def mask.pretty_print(pp)
            pp.text __getobj__
          end
          ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
        end
593
      end
594 595
  end
end