core.rb 18.0 KB
Newer Older
X
Xavier Noria 已提交
1
require 'thread'
2
require 'active_support/core_ext/hash/indifferent_access'
J
Jon Leighton 已提交
3
require 'active_support/core_ext/object/duplicable'
X
Xavier Noria 已提交
4
require 'active_support/core_ext/string/filters'
5 6

module ActiveRecord
J
Jon Leighton 已提交
7 8
  module Core
    extend ActiveSupport::Concern
9

J
Jon Leighton 已提交
10
    included do
J
Jon Leighton 已提交
11 12
      ##
      # :singleton-method:
J
Jon Leighton 已提交
13 14 15 16 17
      #
      # 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
18

J
Jon Leighton 已提交
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
      ##
      # Contains the database configuration - as is typically stored in config/database.yml -
      # as a Hash.
      #
      # 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:
      #
      #   {
      #      'development' => {
      #         'adapter'  => 'sqlite3',
      #         'database' => 'db/development.sqlite3'
      #      },
      #      'production' => {
      #         'adapter'  => 'sqlite3',
      #         'database' => 'db/production.sqlite3'
      #      }
      #   }
45 46 47
      def self.configurations=(config)
        @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
      end
J
Jon Leighton 已提交
48 49
      self.configurations = {}

50 51 52 53 54
      # Returns fully resolved configurations hash
      def self.configurations
        @@configurations
      end

J
Jon Leighton 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
      ##
      # :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.
      mattr_accessor :default_timezone, instance_writer: false
      self.default_timezone = :utc

      ##
      # :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.
      mattr_accessor :schema_format, instance_writer: false
      self.schema_format = :ruby

      ##
      # :singleton-method:
      # Specify whether or not to use timestamps for migration versions
      mattr_accessor :timestamped_migrations, instance_writer: false
      self.timestamped_migrations = true

79 80 81 82 83 84 85 86 87
      ##
      # :singleton-method:
      # Specify whether schema dump should happen at the end of the
      # db:migrate rake task. This is true by default, which is useful for the
      # development environment. This should ideally be false in the production
      # environment where dumping schema is rarely needed.
      mattr_accessor :dump_schema_after_migration, instance_writer: false
      self.dump_schema_after_migration = true

88 89 90 91 92 93 94 95 96
      ##
      # :singleton-method:
      # Specifies which database schemas to dump when calling db:structure:dump.
      # If :schema_search_path (the default), it will dumps any schemas listed in schema_search_path.
      # Use :all to always dumps all schemas regardless of the schema_search_path.
      # A string of comma separated schemas can also be used to pass a custom list of schemas.
      mattr_accessor :dump_schemas, instance_writer: false
      self.dump_schemas = :schema_search_path

97 98
      ##
      # :singleton-method:
99 100 101 102
      # 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.
103 104 105
      mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
      self.warn_on_records_fetched_greater_than = nil

106 107
      mattr_accessor :maintain_test_schema, instance_accessor: false

108 109
      mattr_accessor :belongs_to_required_by_default, instance_accessor: false

110
      class_attribute :default_connection_handler, instance_writer: false
111

112
      def self.connection_handler
113
        ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
114 115 116
      end

      def self.connection_handler=(handler)
117
        ActiveRecord::RuntimeRegistry.connection_handler = handler
118 119 120
      end

      self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
121 122 123
    end

    module ClassMethods
124 125 126 127 128
      def allocate
        define_attribute_methods
        super
      end

129
      def initialize_find_by_cache # :nodoc:
130
        @find_by_statement_cache = {}.extend(Mutex_m)
131 132
      end

133
      def inherited(child_class) # :nodoc:
134
        # initialize cache at class definition for thread safety
135 136 137 138
        child_class.initialize_find_by_cache
        super
      end

139
      def find(*ids) # :nodoc:
140 141 142 143
        # We don't have cache keys for this stuff yet
        return super unless ids.length == 1
        return super if block_given? ||
                        primary_key.nil? ||
144
                        scope_attributes? ||
145 146 147 148
                        columns_hash.include?(inheritance_column) ||
                        ids.first.kind_of?(Array)

        id  = ids.first
149 150
        if ActiveRecord::Base === id
          id = id.id
X
Xavier Noria 已提交
151 152 153 154
          ActiveSupport::Deprecation.warn(<<-MSG.squish)
            You are passing an instance of ActiveRecord::Base to `find`.
            Please pass the id of the object by calling `.id`
          MSG
155
        end
156 157 158

        key = primary_key

159 160
        statement = cached_find_by_statement(key) { |params|
          where(key => params.bind).limit(1)
161
        }
162
        record = statement.execute([id], self, connection).first
163 164 165 166
        unless record
          raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
        end
        record
167 168
      rescue RangeError
        raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
169 170
      end

171
      def find_by(*args) # :nodoc:
172
        return super if scope_attributes? || !(Hash === args.first) || reflect_on_all_aggregations.any?
173 174 175

        hash = args.first

176 177 178
        return super if hash.values.any? { |v|
          v.nil? || Array === v || Hash === v
        }
179

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

183
        keys = hash.keys
184

185 186
        statement = cached_find_by_statement(keys) { |params|
          wheres = keys.each_with_object({}) { |param, o|
187
            o[param] = params.bind
188
          }
189
          where(wheres).limit(1)
190 191
        }
        begin
192
          statement.execute(hash.values, self, connection).first
193 194
        rescue TypeError => e
          raise ActiveRecord::StatementInvalid.new(e.message, e)
195 196
        rescue RangeError
          nil
197 198 199
        end
      end

200
      def find_by!(*args) # :nodoc:
201
        find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}")
202 203
      end

204
      def initialize_generated_modules # :nodoc:
205
        generated_association_methods
206 207
      end

208 209 210
      def generated_association_methods
        @generated_association_methods ||= begin
          mod = const_set(:GeneratedAssociationMethods, Module.new)
211 212 213 214 215 216 217 218 219 220 221
          include mod
          mod
        end
      end

      # Returns a string like 'Post(id:integer, title:string, body:text)'
      def inspect
        if self == Base
          super
        elsif abstract_class?
          "#{super}(abstract)"
222
        elsif !connected?
A
Arun Agrawal 已提交
223
          "#{super} (call '#{super}.connection' to establish a connection)"
224
        elsif table_exists?
225
          attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ', '
226 227 228 229 230 231 232 233 234 235 236
          "#{super}(#{attr_list})"
        else
          "#{super}(Table doesn't exist)"
        end
      end

      # Overwrite the default class equality method to provide support for association proxies.
      def ===(object)
        object.is_a?(self)
      end

P
Peter Suschlik 已提交
237
      # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
O
Oscar Del Ben 已提交
238 239
      #
      #   class Post < ActiveRecord::Base
240
      #     scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
O
Oscar Del Ben 已提交
241
      #   end
242
      def arel_table # :nodoc:
243
        @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
244 245
      end

246
      # Returns the Arel engine.
247
      def arel_engine # :nodoc:
248
        @arel_engine ||=
J
Jon Leighton 已提交
249 250 251 252 253
          if Base == self || connection_handler.retrieve_connection_pool(self)
            self
          else
            superclass.arel_engine
          end
254 255
      end

256
      def predicate_builder # :nodoc:
257
        @predicate_builder ||= PredicateBuilder.new(table_metadata)
258 259
      end

260 261 262 263
      def type_caster # :nodoc:
        TypeCaster::Map.new(self)
      end

264 265
      private

266 267 268 269 270 271
      def cached_find_by_statement(key, &block) # :nodoc:
        @find_by_statement_cache[key] || @find_by_statement_cache.synchronize {
          @find_by_statement_cache[key] ||= StatementCache.create(connection, &block)
        }
      end

272
      def relation # :nodoc:
273
        relation = Relation.create(self, arel_table, predicate_builder)
274 275

        if finder_needs_type_condition?
276
          relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
277
        else
278
          relation
279 280
        end
      end
281 282 283 284

      def table_metadata # :nodoc:
        TableMetadata.new(self, arel_table)
      end
285 286 287 288 289 290 291
    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.
    #
292
    # ==== Example:
293
    #   # Instantiates a single new object
A
AvnerCohen 已提交
294
    #   User.new(first_name: 'Jamie')
295
    def initialize(attributes = nil)
296
      @attributes = self.class._default_attributes.dup
297
      self.class.define_attribute_methods
298 299

      init_internals
300
      initialize_internals_callback
301

302
      assign_attributes(attributes) if attributes
303 304

      yield self if block_given?
305
      run_callbacks :initialize
306 307
    end

308 309 310
    # Initialize an empty model object from +coder+. +coder+ should be
    # the result of previously encoding an Active Record model, using
    # `encode_with`
311 312 313 314
    #
    #   class Post < ActiveRecord::Base
    #   end
    #
315 316 317 318
    #   old_post = Post.new(title: "hello world")
    #   coder = {}
    #   old_post.encode_with(coder)
    #
319
    #   post = Post.allocate
320
    #   post.init_with(coder)
321 322
    #   post.title # => 'hello world'
    def init_with(coder)
323
      coder = LegacyYamlAdapter.convert(self.class, coder)
324
      @attributes = coder['attributes']
325

326 327
      init_internals

328
      @new_record = coder['new_record']
329

330 331
      self.class.define_attribute_methods

332 333
      run_callbacks :find
      run_callbacks :initialize
334 335 336

      self
    end
337

338 339 340 341
    ##
    # :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 已提交
342
    # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
343 344 345 346 347 348 349 350 351
    #
    #   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
352
    #
353
    #   user.name.object_id == user.dup.name.object_id  # => false
354

355 356
    ##
    # :method: dup
357 358 359 360 361 362
    # 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).
363

364
    ##
365
    def initialize_dup(other) # :nodoc:
366
      @attributes = @attributes.dup
367
      @attributes.reset(self.class.primary_key)
368

369
      run_callbacks(:initialize)
370 371

      @new_record  = true
372
      @destroyed   = false
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387

      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)
388
    #   coder # => {"attributes" => {"id" => nil, ... }}
389
    def encode_with(coder)
390 391
      # FIXME: Remove this when we better serialize attributes
      coder['raw_attributes'] = attributes_before_type_cast
392
      coder['attributes'] = @attributes
393
      coder['new_record'] = new_record?
394
      coder['active_record_yaml_version'] = 1
395 396 397 398 399 400 401 402 403 404 405 406 407 408
    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) &&
409
        !id.nil? &&
410 411 412 413 414 415 416
        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
417 418 419 420 421
      if id
        id.hash
      else
        super
      end
422 423
    end

424 425 426
    # Clone and freeze the attributes hash such that associations are still
    # accessible, even on destroyed records, but cloned models will not be
    # frozen.
427
    def freeze
428
      @attributes = @attributes.clone.freeze
429
      self
430 431 432 433
    end

    # Returns +true+ if the attributes hash has been frozen.
    def frozen?
434
      @attributes.frozen?
435 436
    end

437 438 439 440 441 442 443 444 445
    # Allows sort on objects
    def <=>(other_object)
      if other_object.is_a?(self.class)
        self.to_key <=> other_object.to_key
      else
        super
      end
    end

446 447 448 449 450 451 452 453 454 455 456
    # 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

457 458 459 460
    def connection_handler
      self.class.connection_handler
    end

461 462
    # Returns the contents of the record as a nicely formatted string.
    def inspect
463
      # We check defined?(@attributes) not to issue warnings if the object is
464
      # allocated but not initialized.
465
      inspection = if defined?(@attributes) && @attributes
466 467 468 469 470 471 472 473 474 475 476
                     self.class.column_names.collect { |name|
                       if has_attribute?(name)
                         "#{name}: #{attribute_for_inspect(name)}"
                       end
                     }.compact.join(", ")
                   else
                     "not initialized"
                   end
      "#<#{self.class} #{inspection}>"
    end

477
    # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
478 479
    # when pp is required.
    def pretty_print(pp)
480
      return super if custom_inspect_method_defined?
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
      pp.object_address_group(self) do
        if defined?(@attributes) && @attributes
          column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
          pp.seplist(column_names, proc { pp.text ',' }) do |column_name|
            column_value = read_attribute(column_name)
            pp.breakable ' '
            pp.group(1) do
              pp.text column_name
              pp.text ':'
              pp.breakable
              pp.pp column_value
            end
          end
        else
          pp.breakable ' '
          pp.text 'not initialized'
        end
      end
    end

501 502
    # Returns a hash of the given methods with their names as keys and returned values as values.
    def slice(*methods)
503
      Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
504 505
    end

506 507
    private

508 509 510 511 512 513 514
    # Under Ruby 1.9, 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.
    #
515
    # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
516 517 518
    def to_ary # :nodoc:
      nil
    end
519 520

    def init_internals
521 522 523
      @readonly                 = false
      @destroyed                = false
      @marked_for_destruction   = false
524
      @destroyed_by_association = nil
525 526 527
      @new_record               = true
      @txn                      = nil
      @_start_transaction_state = {}
528
      @transaction_state        = nil
529
    end
530

531
    def initialize_internals_callback
532
    end
533

534 535
    def thaw
      if frozen?
536
        @attributes = @attributes.dup
537 538
      end
    end
539 540 541 542

    def custom_inspect_method_defined?
      self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
    end
543 544
  end
end