core.rb 15.8 KB
Newer Older
1
require 'active_support/core_ext/hash/indifferent_access'
J
Jon Leighton 已提交
2
require 'active_support/core_ext/object/duplicable'
J
Jon Leighton 已提交
3
require 'thread'
4 5

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

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

J
Jon Leighton 已提交
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
      ##
      # :singleton-method:
      # 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
      # :nodoc:
      mattr_accessor :maintain_test_schema, instance_accessor: false

82 83 84 85 86
      def self.disable_implicit_join_references=(value)
        ActiveSupport::Deprecation.warn("Implicit join references were removed with Rails 4.1." \
                                        "Make sure to remove this configuration because it does nothing.")
      end

87
      class_attribute :default_connection_handler, instance_writer: false
88

89
      def self.connection_handler
90
        ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
91 92 93
      end

      def self.connection_handler=(handler)
94
        ActiveRecord::RuntimeRegistry.connection_handler = handler
95 96 97
      end

      self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
98 99 100
    end

    module ClassMethods
101
      def initialize_generated_modules
102
        super
103

104
        generated_association_methods
105 106
      end

107 108 109
      def generated_association_methods
        @generated_association_methods ||= begin
          mod = const_set(:GeneratedAssociationMethods, Module.new)
110 111 112 113 114 115 116 117 118 119 120
          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)"
121
        elsif !connected?
A
Arun Agrawal 已提交
122
          "#{super} (call '#{super}.connection' to establish a connection)"
123 124 125 126 127 128 129 130 131 132 133 134 135
        elsif table_exists?
          attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
          "#{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 已提交
136
      # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
O
Oscar Del Ben 已提交
137 138
      #
      #   class Post < ActiveRecord::Base
139
      #     scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
O
Oscar Del Ben 已提交
140
      #   end
141 142 143 144
      def arel_table
        @arel_table ||= Arel::Table.new(table_name, arel_engine)
      end

145
      # Returns the Arel engine.
146
      def arel_engine
147
        @arel_engine ||=
J
Jon Leighton 已提交
148 149 150 151 152
          if Base == self || connection_handler.retrieve_connection_pool(self)
            self
          else
            superclass.arel_engine
          end
153 154 155 156 157
      end

      private

      def relation #:nodoc:
158
        relation = Relation.create(self, arel_table)
159 160

        if finder_needs_type_condition?
161
          relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
162
        else
163
          relation
164 165 166 167 168 169 170 171 172
        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.
    #
173
    # ==== Example:
174
    #   # Instantiates a single new object
A
AvnerCohen 已提交
175
    #   User.new(first_name: 'Jamie')
176
    def initialize(attributes = nil, options = {})
J
Jon Leighton 已提交
177 178 179 180
      defaults = self.class.column_defaults.dup
      defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }

      @attributes   = self.class.initialize_attributes(defaults)
181 182
      @column_types_override = nil
      @column_types = self.class.column_types
183 184

      init_internals
185
      init_changed_attributes
186 187 188
      ensure_proper_type
      populate_with_current_scope_attributes

189 190 191
      # +options+ argument is only needed to make protected_attributes gem easier to hook.
      # Remove it when we drop support to this gem.
      init_attributes(attributes, options) if attributes
192 193

      yield self if block_given?
J
Jon Leighton 已提交
194
      run_callbacks :initialize unless _initialize_callbacks.empty?
195 196 197 198 199 200 201 202 203 204 205 206 207
    end

    # Initialize an empty model object from +coder+. +coder+ must contain
    # the attributes necessary for initializing an empty model object. For
    # example:
    #
    #   class Post < ActiveRecord::Base
    #   end
    #
    #   post = Post.allocate
    #   post.init_with('attributes' => { 'title' => 'hello world' })
    #   post.title # => 'hello world'
    def init_with(coder)
208
      @attributes   = self.class.initialize_attributes(coder['attributes'])
209 210
      @column_types_override = coder['column_types']
      @column_types = self.class.column_types
211

212 213
      init_internals

214
      @new_record = false
215

216 217 218 219 220
      run_callbacks :find
      run_callbacks :initialize

      self
    end
221

222 223 224 225
    ##
    # :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 已提交
226
    # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
227 228 229 230 231 232 233 234 235
    #
    #   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
236
    #
237
    #   user.name.object_id == user.dup.name.object_id  # => false
238

239 240
    ##
    # :method: dup
241 242 243 244 245 246
    # 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).
247

248
    ##
249
    def initialize_dup(other) # :nodoc:
250
      cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
251
      self.class.initialize_attributes(cloned_attributes, :serialized => false)
252

253
      @attributes = cloned_attributes
254
      @attributes[self.class.primary_key] = nil
255

256
      run_callbacks(:initialize) unless _initialize_callbacks.empty?
257 258

      @changed_attributes = {}
259
      init_changed_attributes
260

261
      @aggregation_cache = {}
262
      @association_cache = {}
263 264
      @attributes_cache  = {}

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
      @new_record  = true

      ensure_proper_type
      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)
282
    #   coder # => {"attributes" => {"id" => nil, ... }}
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
    def encode_with(coder)
      coder['attributes'] = attributes
    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) &&
299
        id &&
300 301 302 303 304 305 306 307 308 309
        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.hash
    end

310 311 312
    # Clone and freeze the attributes hash such that associations are still
    # accessible, even on destroyed records, but cloned models will not be
    # frozen.
313
    def freeze
314
      @attributes = @attributes.clone.freeze
315
      self
316 317 318 319 320 321 322
    end

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

323 324 325 326 327 328 329 330 331
    # 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

332 333 334 335 336 337 338 339 340 341 342
    # 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

343 344 345 346
    def connection_handler
      self.class.connection_handler
    end

347 348
    # Returns the contents of the record as a nicely formatted string.
    def inspect
349 350 351
      # We check defined?(@attributes) not to issue warnings if the object is
      # allocated but not initialized.
      inspection = if defined?(@attributes) && @attributes
352 353 354 355 356 357 358 359 360 361 362
                     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

363 364
    # Returns a hash of the given methods with their names as keys and returned values as values.
    def slice(*methods)
365
      Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
366 367
    end

368 369 370 371 372 373 374 375
    def set_transaction_state(state) # :nodoc:
      @transaction_state = state
    end

    def has_transactional_callbacks? # :nodoc:
      !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_create_callbacks.empty?
    end

376 377
    private

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
    # Updates the attributes on this particular ActiveRecord object so that
    # if it is associated with a transaction, then the state of the AR object
    # will be updated to reflect the current state of the transaction
    #
    # The @transaction_state variable stores the states of the associated
    # transaction. This relies on the fact that a transaction can only be in
    # one rollback or commit (otherwise a list of states would be required)
    # Each AR object inside of a transaction carries that transaction's
    # TransactionState.
    #
    # This method checks to see if the ActiveRecord object's state reflects
    # the TransactionState, and rolls back or commits the ActiveRecord object
    # as appropriate.
    #
    # Since ActiveRecord objects can be inside multiple transactions, this
    # method recursively goes through the parent of the TransactionState and
    # checks if the ActiveRecord object reflects the state of the object.
    def sync_with_transaction_state
      update_attributes_from_transaction_state(@transaction_state, 0)
    end

    def update_attributes_from_transaction_state(transaction_state, depth)
400
      if transaction_state && transaction_state.finalized? && !has_transactional_callbacks?
401
        unless @reflects_state[depth]
402 403
          restore_transaction_record_state if transaction_state.rolledback?
          clear_transaction_record_state
404 405 406 407 408 409 410 411 412
          @reflects_state[depth] = true
        end

        if transaction_state.parent && !@reflects_state[depth+1]
          update_attributes_from_transaction_state(transaction_state.parent, depth+1)
        end
      end
    end

413 414 415 416 417 418 419
    # 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.
    #
420
    # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
421 422 423
    def to_ary # :nodoc:
      nil
    end
424 425

    def init_internals
426 427 428
      pk = self.class.primary_key
      @attributes[pk] = nil unless @attributes.key?(pk)

429 430 431 432 433 434
      @aggregation_cache        = {}
      @association_cache        = {}
      @attributes_cache         = {}
      @readonly                 = false
      @destroyed                = false
      @marked_for_destruction   = false
435
      @destroyed_by_association = nil
436 437 438
      @new_record               = true
      @txn                      = nil
      @_start_transaction_state = {}
439 440
      @transaction_state        = nil
      @reflects_state           = [false]
441
    end
442 443

    def init_changed_attributes
444
      # Intentionally avoid using #column_defaults since overridden defaults (as is done in
445 446 447
      # optimistic locking) won't get written unless they get marked as changed
      self.class.columns.each do |c|
        attr, orig_value = c.name, c.default
448
        changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
449 450
      end
    end
451 452 453 454 455 456

    # This method is needed to make protected_attributes gem easier to hook.
    # Remove it when we drop support to this gem.
    def init_attributes(attributes, options)
      assign_attributes(attributes)
    end
457 458
  end
end