require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' require 'thread' module ActiveRecord module Core extend ActiveSupport::Concern included do ## # :singleton-method: # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, # 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+. config_attribute :logger, :global => true ## # :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' # } # } config_attribute :configurations, :global => true self.configurations = {} ## # :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. config_attribute :default_timezone, :global => true 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. config_attribute :schema_format, :global => true self.schema_format = :ruby ## # :singleton-method: # Specify whether or not to use timestamps for migration versions config_attribute :timestamped_migrations, :global => true self.timestamped_migrations = true ## # :singleton-method: # The connection handler config_attribute :connection_handler self.connection_handler = ConnectionAdapters::ConnectionHandler.new ## # :singleton-method: # Specifies wether or not has_many or has_one association option # :dependent => :restrict raises an exception. If set to true, the # ActiveRecord::DeleteRestrictionError exception will be raised # along with a DEPRECATION WARNING. If set to false, an error would # be added to the model instead. config_attribute :dependent_restrict_raises, :global => true self.dependent_restrict_raises = true end module ClassMethods def inherited(child_class) #:nodoc: child_class.initialize_generated_modules super end def initialize_generated_modules @attribute_methods_mutex = Mutex.new # force attribute methods to be higher in inheritance hierarchy than other generated methods generated_attribute_methods generated_feature_methods end def generated_feature_methods @generated_feature_methods ||= begin mod = const_set(:GeneratedFeatureMethods, Module.new) 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)" 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 def arel_table @arel_table ||= Arel::Table.new(table_name, arel_engine) end def arel_engine @arel_engine ||= connection_handler.retrieve_connection_pool(self) ? self : active_record_super.arel_engine end private def relation #:nodoc: relation = Relation.new(self, arel_table) if finder_needs_type_condition? relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) else relation 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. # # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options # in the +options+ parameter. # # ==== Examples # # Instantiates a single new object # User.new(:first_name => 'Jamie') # # # Instantiates a single new object using the :admin mass-assignment security role # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) # # # Instantiates a single new object bypassing mass-assignment security # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) def initialize(attributes = nil, options = {}) @attributes = self.class.initialize_attributes(self.class.column_defaults.dup) @columns_hash = self.class.column_types.dup init_internals ensure_proper_type populate_with_current_scope_attributes assign_attributes(attributes, options) if attributes yield self if block_given? run_callbacks :initialize if _initialize_callbacks.any? 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) @attributes = self.class.initialize_attributes(coder['attributes']) @columns_hash = self.class.column_types.merge(coder['column_types'] || {}) init_internals @new_record = false run_callbacks :find run_callbacks :initialize self end # 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). def initialize_dup(other) cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) self.class.initialize_attributes(cloned_attributes) cloned_attributes.delete(self.class.primary_key) @attributes = cloned_attributes @attributes[self.class.primary_key] = nil run_callbacks(:initialize) if _initialize_callbacks.any? @changed_attributes = {} self.class.column_defaults.each do |attr, orig_value| @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr]) end @aggregation_cache = {} @association_cache = {} @attributes_cache = {} @new_record = true ensure_proper_type populate_with_current_scope_attributes 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) 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) && id.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.hash end # Freeze the attributes hash such that associations are still accessible, even on destroyed records. def freeze @attributes.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) self.to_key <=> other_object.to_key else nil end end # 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 # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work that isn't # easily done without going straight to SQL. def connection self.class.connection end # Returns the contents of the record as a nicely formatted string. def inspect inspection = if @attributes 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 # Returns a hash of the given methods with their names as keys and returned values as values. def slice(*methods) Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access end private # 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. # # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ def to_ary # :nodoc: nil end def init_internals pk = self.class.primary_key @attributes[pk] = nil unless @attributes.key?(pk) @aggregation_cache = {} @association_cache = {} @attributes_cache = {} @previously_changed = {} @changed_attributes = {} @readonly = false @destroyed = false @marked_for_destruction = false @new_record = true end end end