Active Record – Object-relational mapping in Rails

Active Record connects classes to relational database tables to establish an almost zero-configuration persistence layer for applications. The library provides a base class that, when subclassed, sets up a mapping between the new class and an existing table in the database. In the context of an application, these classes are commonly referred to as models. Models can also be connected to other models; this is done by defining associations.

Active Record relies heavily on naming in that it uses class and association names to establish mappings between respective database tables and foreign key columns. Although these mappings can be defined explicitly, it’s recommended to follow naming conventions, especially when getting started with the library.

You can read more about Active Record in the Active Record Basics guide.

A short rundown of some of the major features:

  • Automated mapping between classes and tables, attributes and columns.

    class Product < ActiveRecord::Base
    end
    

    The Product class is automatically mapped to the table named “products”, which might look like this:

    CREATE TABLE products (
      id bigint NOT NULL auto_increment,
      name varchar(255),
      PRIMARY KEY  (id)
    );
    

    This would also define the following accessors: Product#name and Product#name=(new_name).

    Learn more

  • Associations between objects defined by simple class methods.

    class Firm < ActiveRecord::Base
      has_many   :clients
      has_one    :account
      belongs_to :conglomerate
    end
    

    Learn more

  • Aggregations of value objects.

    class Account < ActiveRecord::Base
      composed_of :balance, class_name: 'Money',
                  mapping: %w(balance amount)
      composed_of :address,
                  mapping: [%w(address_street street), %w(address_city city)]
    end
    

    Learn more

  • Validation rules that can differ for new or existing objects.

    class Account < ActiveRecord::Base
      validates :subdomain, :name, :email_address, :password, presence: true
      validates :subdomain, uniqueness: true
      validates :terms_of_service, acceptance: true, on: :create
      validates :password, :email_address, confirmation: true, on: :create
    end
    

    Learn more

  • Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.).

    class Person < ActiveRecord::Base
      before_destroy :invalidate_payment_plan
      # the `invalidate_payment_plan` method gets called just before Person#destroy
    end
    

    Learn more

  • Inheritance hierarchies.

    class Company < ActiveRecord::Base; end
    class Firm < Company; end
    class Client < Company; end
    class PriorityClient < Client; end
    

    Learn more

  • Transactions.

    # Database transaction
    Account.transaction do
      david.withdrawal(100)
      mary.deposit(100)
    end
    

    Learn more

  • Reflections on columns, associations, and aggregations.

    reflection = Firm.reflect_on_association(:clients)
    reflection.klass # => Client (class)
    Firm.columns # Returns an array of column descriptors for the firms table
    

    Learn more

  • Database abstraction through simple adapters.

    # connect to SQLite3
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'dbfile.sqlite3')
    
    # connect to MySQL with authentication
    ActiveRecord::Base.establish_connection(
      adapter:  'mysql2',
      host:     'localhost',
      username: 'me',
      password: 'secret',
      database: 'activerecord'
    )
    

    Learn more and read about the built-in support for MySQL, PostgreSQL, and SQLite3.

  • Logging support for Log4r and Logger.

    ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
    ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
    
  • Database agnostic schema management with Migrations.

    class AddSystemSettings < ActiveRecord::Migration[7.1]
      def up
        create_table :system_settings do |t|
          t.string  :name
          t.string  :label
          t.text    :value
          t.string  :type
          t.integer :position
        end
    
        SystemSetting.create name: 'notice', label: 'Use notice?', value: 1
      end
    
      def down
        drop_table :system_settings
      end
    end
    

    Learn more

Philosophy

Active Record is an implementation of the object-relational mapping (ORM) pattern by the same name described by Martin Fowler:

"An object that wraps a row in a database table or view,
encapsulates the database access, and adds domain logic on that data."

Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is object-relational mapping. The prime directive for this mapping has been to minimize the amount of code needed to build a real-world domain model. This is made possible by relying on a number of conventions that make it easy for Active Record to infer complex relations and structures from a minimal amount of explicit direction.

Convention over Configuration:

  • No XML files!

  • Lots of reflection and run-time extension

  • Magic is not inherently a bad word

Admit the Database:

  • Lets you drop down to SQL for odd cases and performance

  • Doesn’t attempt to duplicate or replace data definitions

Download and installation

The latest version of Active Record can be installed with RubyGems:

$ gem install activerecord

Source code can be downloaded as part of the Rails project on GitHub:

License

Active Record is released under the MIT license:

Support

API documentation is at:

Bug reports for the Ruby on Rails project can be filed here:

Feature requests should be discussed on the rails-core mailing list here:

Namespace

Module

Class

Methods

Included Modules

Constants

MigrationProxy = Struct.new(:name, :version, :filename, :scope) do def initialize(name, version, filename, scope) super @migration = nil end def basename File.basename(filename) end delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private def migration @migration ||= load_migration end def load_migration Object.send(:remove_const, name) rescue nil load(File.expand_path(filename)) name.constantize.new(name, version) end end
 

MigrationProxy is used to defer loading of the actual migration classes until they are needed

Point = Struct.new(:x, :y)
UnknownAttributeError = ActiveModel::UnknownAttributeError
 

Active Model UnknownAttributeError

Raised when unknown attributes are supplied via mass assignment.

class Person
  include ActiveModel::AttributeAssignment
  include ActiveModel::Validations
end

person = Person.new
person.assign_attributes(name: 'Gorby')
# => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person.

Attributes

[RW] application_record_class
[RW] async_query_executor
[RW] before_committed_on_all_records
[RW] belongs_to_required_validates_foreign_key
[RW] commit_transaction_on_non_local_return
[R] db_warnings_action
[RW] db_warnings_ignore
[R] default_timezone
[RW] disable_prepared_statements
[RW] index_nested_attribute_errors
[RW] lazily_load_schema_cache
[RW] maintain_test_schema
[RW] query_transformers
[RW] raise_on_assign_to_attr_readonly
[RW] reading_role
[RW] run_after_transaction_callbacks_in_order_defined
[RW] schema_cache_ignored_tables
[RW] writing_role

Class Public methods

db_warnings_action=(action)

📝 Source code
# File activerecord/lib/active_record.rb, line 211
  def self.db_warnings_action=(action)
    @db_warnings_action =
      case action
      when :ignore
        nil
      when :log
        ->(warning) do
          warning_message = "[#{warning.class}] #{warning.message}"
          warning_message += " (#{warning.code})" if warning.code
          ActiveRecord::Base.logger.warn(warning_message)
        end
      when :raise
        ->(warning) { raise warning }
      when :report
        ->(warning) { Rails.error.report(warning, handled: true) }
      when Proc
        action
      else
        raise ArgumentError, "db_warnings_action must be one of :ignore, :log, :raise, :report, or a custom proc."
      end
  end
🔎 See on GitHub

default_timezone=(default_timezone)

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.

📝 Source code
# File activerecord/lib/active_record.rb, line 196
  def self.default_timezone=(default_timezone)
    unless %i(local utc).include?(default_timezone)
      raise ArgumentError, "default_timezone must be either :utc (default) or :local."
    end

    @default_timezone = default_timezone
  end
🔎 See on GitHub

disconnect_all!()

Explicitly closes all database connections in all pools.

📝 Source code
# File activerecord/lib/active_record.rb, line 476
  def self.disconnect_all!
    ConnectionAdapters::PoolConfig.disconnect_all!
  end
🔎 See on GitHub

eager_load!()

📝 Source code
# File activerecord/lib/active_record.rb, line 465
  def self.eager_load!
    super
    ActiveRecord::Locking.eager_load!
    ActiveRecord::Scoping.eager_load!
    ActiveRecord::Associations.eager_load!
    ActiveRecord::AttributeMethods.eager_load!
    ActiveRecord::ConnectionAdapters.eager_load!
    ActiveRecord::Encryption.eager_load!
  end
🔎 See on GitHub

gem_version()

Returns the currently loaded version of Active Record as a Gem::Version.

📝 Source code
# File activerecord/lib/active_record/gem_version.rb, line 5
  def self.gem_version
    Gem::Version.new VERSION::STRING
  end
🔎 See on GitHub

global_executor_concurrency=(global_executor_concurrency)

Set the global_executor_concurrency. This configuration value can only be used with the global thread pool async query executor.

📝 Source code
# File activerecord/lib/active_record.rb, line 278
  def self.global_executor_concurrency=(global_executor_concurrency)
    if self.async_query_executor.nil? || self.async_query_executor == :multi_thread_pool
      raise ArgumentError, "`global_executor_concurrency` cannot be set when using the executor is nil or set to multi_thead_pool. For multiple thread pools, please set the concurrency in your database configuration."
    end

    @global_executor_concurrency = global_executor_concurrency
  end
🔎 See on GitHub

legacy_connection_handling=(_)

📝 Source code
# File activerecord/lib/active_record.rb, line 245
  def self.legacy_connection_handling=(_)
    raise ArgumentError, <<~MSG.squish
      The `legacy_connection_handling` setter was deprecated in 7.0 and removed in 7.1,
      but is still defined in your configuration. Please remove this call as it no longer
      has any effect."
    MSG
  end
🔎 See on GitHub

marshalling_format_version()

📝 Source code
# File activerecord/lib/active_record.rb, line 457
  def self.marshalling_format_version
    Marshalling.format_version
  end
🔎 See on GitHub

marshalling_format_version=(value)

📝 Source code
# File activerecord/lib/active_record.rb, line 461
  def self.marshalling_format_version=(value)
    Marshalling.format_version = value
  end
🔎 See on GitHub

suppress_multiple_database_warning()

📝 Source code
# File activerecord/lib/active_record.rb, line 395
  def self.suppress_multiple_database_warning
    ActiveRecord.deprecator.warn(<<-MSG.squish)
      config.active_record.suppress_multiple_database_warning is deprecated and will be removed in Rails 7.2.
      It no longer has any effect and should be removed from the configuration file.
    MSG
  end
🔎 See on GitHub

suppress_multiple_database_warning=(value)

📝 Source code
# File activerecord/lib/active_record.rb, line 402
  def self.suppress_multiple_database_warning=(value)
    ActiveRecord.deprecator.warn(<<-MSG.squish)
      config.active_record.suppress_multiple_database_warning= is deprecated and will be removed in Rails 7.2.
      It no longer has any effect and should be removed from the configuration file.
    MSG
  end
🔎 See on GitHub

unknown

Specifies if the methods calling database queries should be logged below their relevant queries. Defaults to false.

📝 Source code
# File activerecord/lib/active_record.rb, line 298
  singleton_class.attr_accessor :verbose_query_logs
🔎 See on GitHub

version()

Returns the currently loaded version of Active Record as a Gem::Version.

📝 Source code
# File activerecord/lib/active_record/version.rb, line 7
  def self.version
    gem_version
  end
🔎 See on GitHub