ActiveRecord vs. Ecto - Part One
Tags: elixir, ruby, programming, beginners
Published: 2018-09-28 11:22:43

---
title: ActiveRecord vs. Ecto - Part One
description: In part one of this two-part series, we'll compare ActiveRecord and Ecto by looking at how they handle database schemas and migration.
tags: elixir, ruby, programming, beginners
published: true
cover_image: https://thepracticaldev.s3.amazonaws.com/i/44kn274x7d1o4wlhl7ti.jpg
canonical_url: https://blog.appsignal.com/2018/09/28/active-record-vs-ecto.html
---

Data is a core part of most software applications. Mapping and querying data from a database is a recurring task in the life of a developer. Because of this, it is important to understand the process and be able to use abstractions that simplify the task.

In this post, the first of a series of two, you'll find a comparison between [ActiveRecord](http://guides.rubyonrails.org/active_record_basics.html) (Ruby) and [Ecto](https://hexdocs.pm/ecto/Ecto.html) (Elixir). We'll see how both tools enable developers to migrate and map database schemas.

So we’ll be comparing Apples and Oranges. (Original) Batgirl, who never needed to say a word, versus Batman, explicitly stating 'I'm Batman'. Implicit, convention over configuration, versus Explicit intention. Round one. Fight!

## ActiveRecord

With more than 10 years since its release, chances are, you've already heard about ActiveRecord - the famous [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) that is shipped by default with Ruby on Rails projects.

> <em>ActiveRecord</em> is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. ActiveRecord facilitates the creation and use of business objects whose data requires persistent storage in a database. It is an implementation of the ActiveRecord pattern which is itself, a description of an Object Relational Mapping system.

Although it is mostly known to be used with Rails, ActiveRecord can also be used as a standalone tool, getting embedded in other projects.

## Ecto

When compared to ActiveRecord, Ecto is a quite new (and at the moment not as famous) tool. It is written in Elixir and is included by default in Phoenix projects.

Unlike ActiveRecord, Ecto is not an ORM, but a library that enables the use of Elixir to write queries and interact with the database.

><em>Ecto</em> is a domain specific language for writing queries and interacting with databases in Elixir.

By design, Ecto is a standalone tool, being used in different Elixir projects and not connected to any framework.

## Aren't you Comparing Apples and Oranges?

Yes we are! Although ActiveRecord and Ecto are semantically different, but common features like database migrations, database mappings, queries and validations are supported by both ActiveRecord and Ecto. And we can achieve the same results are achieved using both tools. For those interested in Elixir coming from a Ruby background we thought this would be an interesting comparison.

## The Invoice System

Throughout the rest of the post, a hypothetical invoice system will be used for demonstration. Let's imagine we have a store selling suits to super heroes. To keep things simple, we'll only have two tables for the invoice system: *users* and *invoices*.

Below is the structure of those tables, with their fields and types:

__users__

| Field                                             | Type     |
|---------------------------------------------------|----------|
| full_name                                         | string   |
| email                                             | string   |
| created\_at (ActiveRecord) / inserted\_at (Ecto) | datetime |
| updated_at                                        | datetime |

__invoices__

| Field                                             | Type     |
|---------------------------------------------------|----------|
| user_id                                           | integer  |
| payment_method                                    | string   |
| paid_at                                           | datetime |
| created\_at (ActiveRecord) / inserted\_at (Ecto) | datetime |
| updated_at                                        | datetime |


The users table has four fields: *full_name*, *email*, *updated_at* and a fourth field that is dependent on the tool used. ActiveRecord creates a *created_at* field while Ecto creates an *inserted_at* field to represent the timestamp of the moment the record was first inserted in the database.

The second table is named *invoices*. It has five fields: *user_id*, *payment_method*, *paid_at*, *updated_at* and, similar to the users table, either *created_at* or *inserted_at*, depending on the tool used.

The users and invoices tables have the following associations:

* A user has many invoices
* An invoice belongs to a user

## Migrations

Migrations allow developers to easily evolve their database schema over time, using an iterative process. Both ActiveRecord and Ecto enable developers to migrate database schema using a high-level language (Ruby and Elixir respectively), instead of directly dealing with [SQL](https://en.wikipedia.org/wiki/SQL).

Let's take a look at how migrations work in ActiveRecord and Ecto by using them to create the users and invoices tables.

### ActiveRecord: Creating the Users Table

__Migration__

```ruby
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :full_name, null: false
      t.string :email, index: {unique: true}, null: false
      t.timestamps
    end
  end
end
```

ActiveRecord migrations enable the creation of tables using the `create_table` method. Although the `created_at` and `updated_at` fields are not defined in the migration file, the use of `t.timestamps` triggers ActiveRecord to create both.

__Created Table Structure__

After running the `CreateUsers` migration, the created table will have the following structure:

```
   Column   |            Type             | Nullable |              Default
------------+-----------------------------+----------+-----------------------------------
 id         | bigint                      | not null | nextval('users_id_seq'::regclass)
 full_name  | character varying           | not null |
 email      | character varying           | not null |
 created_at | timestamp without time zone | not null |
 updated_at | timestamp without time zone | not null |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "index_users_on_email" UNIQUE, btree (email)
```

The migration is also responsible for the creation of a unique index for the email field. The option `index: {unique: true}` is passed to the email field definition. This is why the table has listed the `"index_users_on_email" UNIQUE, btree (email)` index as part of its structure.

### Ecto: Creating the Users Table

__Migration__

```elixir
defmodule Financex.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :full_name, :string, null: false
      add :email, :string, null: false
      timestamps()
    end

    create index(:users, [:email], unique: true)
  end
end
```

The Ecto migration combines the functions `create()` and `table()` to create the users table. The Ecto migration file is quite similar to its ActiveRecord equivalent. In ActiveRecord the timestamps fields (`created_at` and `updated_at`) are created by `t.timestamps` while in Ecto the timestamps fields (`inserted_at` and `updated_at`) are created by the `timestamps()` function.

There's a small difference between both tools on how indexes are created. In ActiveRecord, the index is defined as an option to the field being created. Ecto uses the combination of the functions `create()` and `index()` to achieve that, consistent with how the combination is used to create the table itself.

__Created Table Structure__

```
   Column    |            Type             | Nullable |              Default
-------------+-----------------------------+----------+-----------------------------------
 id          | bigint                      | not null | nextval('users_id_seq'::regclass)
 full_name   | character varying(255)      | not null |
 email       | character varying(255)      | not null |
 inserted_at | timestamp without time zone | not null |
 updated_at  | timestamp without time zone | not null |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "users_email_index" UNIQUE, btree (email)
```

The table created on running the `Financex.Repo.Migrations.CreateUsers` migration has an identical structure to the table created using ActiveRecord.

### ActiveRecord: Creating the `invoices` Table

__Migration__

```ruby
class CreateInvoices < ActiveRecord::Migration[5.2]
  def change
    create_table :invoices do |t|
      t.references :user
      t.string :payment_method
      t.datetime :paid_at
      t.timestamps
    end
  end
end
```

This migration includes the `t.references` method, that wasn't present in the previous one. It is used to create a reference to the users table. As described earlier, a user has many invoices and an invoice belongs to a user. The `t.references` method creates a `user_id` column in the invoices table to hold that reference.


__Created Table Structure__

```
     Column     |            Type             | Nullable |               Default
----------------+-----------------------------+----------+--------------------------------------
 id             | bigint                      | not null | nextval('invoices_id_seq'::regclass)
 user_id        | bigint                      |          |
 payment_method | character varying           |          |
 paid_at        | timestamp without time zone |          |
 created_at     | timestamp without time zone | not null |
 updated_at     | timestamp without time zone | not null |
Indexes:
    "invoices_pkey" PRIMARY KEY, btree (id)
    "index_invoices_on_user_id" btree (user_id)
```

The created table follows the same patterns as the previously created table. The only difference is an extra index (`index_invoices_on_user_id`), which ActiveRecord automatically adds when the `t.references` method is used.

### Ecto: Creating the `invoices` Table

__Migration__

```elixir
defmodule Financex.Repo.Migrations.CreateInvoices do
  use Ecto.Migration

  def change do
    create table(:invoices) do
      add :user_id, references(:users)
      add :payment_method, :string
      add :paid_at, :utc_datetime
      timestamps()
    end

    create index(:invoices, [:user_id])
  end
end
```

Ecto also supports the creation of database references, by using the `references()` function. Unlike ActiveRecord, which infers the column name, Ecto requires the developer to explicitly define the `user_id` column name. The `references()` function also requires the developer to explicitly define the table the reference is pointing to, which in this example, is the users table.

__Created Table Structure__

```
     Column     |            Type             | Nullable |               Default
----------------+-----------------------------+----------+--------------------------------------
 id             | bigint                      | not null | nextval('invoices_id_seq'::regclass)
 user_id        | bigint                      |          |
 payment_method | character varying(255)      |          |
 paid_at        | timestamp without time zone |          |
 inserted_at    | timestamp without time zone | not null |
 updated_at     | timestamp without time zone | not null |

Indexes:
    "invoices_pkey" PRIMARY KEY, btree (id)
    "invoices_user_id_index" btree (user_id)
Foreign-key constraints:
    "invoices_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
```

Both migrations are also quite similar. When it comes to the way the `references` feature is handled, there are a few differences:

1. Ecto creates a foreign-key constraint to the `user_id` field (`"invoices_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)`), which maintains the referential integrity between the users and invoices tables.

2. ActiveRecord automatically creates an index for the `user_id` column. Ecto requires the developer to be explicit about that. This is why the migration has the `create index(:invoices, [:user_id])` statement.

## ActiveRecord: Data Mapping & Associations

ActiveRecord is known for its "conventions over configurations" motto. It infers the database table names using the model class name, by default. A class named `User`, by default, uses the `users` table as its source. ActiveRecord also maps all the columns of the table as an instance attribute. Developers are only required to define the associations among the tables. These are also used by ActiveRecord to infer the involved classes and tables.

Take a look at how the users and invoices tables are mapped using ActiveRecord:

__users__

```ruby
class User < ApplicationRecord
  has_many :invoices
end
```

__invoices__

```ruby
class Invoice < ApplicationRecord
  belongs_to :user
end
```

### Ecto: Data Mapping & Associations

On the other hand, Ecto requires the developer to be explicit about the data source and its fields. Although Ecto has similar `has_many` and `belongs_to` features, it also requires developers to be explicit about the associated table and the schema module that is used to handle that table schema.

This is how Ecto maps the users and invoices tables:

__users__

```elixir
defmodule Financex.Accounts.User do
  use Ecto.Schema

  schema "users" do
    field :full_name, :string
    field :email, :string
    has_many :invoices, Financex.Accounts.Invoice
    timestamps()
  end
end
```

__invoices__

```elixir
defmodule Financex.Accounts.Invoice do
  use Ecto.Schema

  schema "invoices" do
    field :payment_method, :string
    field :paid_at, :utc_datetime
    belongs_to :user, Financex.Accounts.User
    timestamps()
  end
end
```

## Wrap Up

In this post, we compared apples and oranges without a blink. We compared how ActiveRecord and Ecto handle database migrations and mapping. A battle of the implicit slient original Batgirl versus the explicit 'I'm Batman' Batman.

Thanks to "convention over configuration", using ActiveRecord usually involves less writing. Ecto goes in the opposite direction, requiring developers to be more explicit about their intents. Other than "less code" being better in general, ActiveRecord has some optimal defaults in place that save the developer from having to make decisions on everything and also having to understand all the underlying configurations. For beginners, ActiveRecord is a more suitable solution, because it makes "good enough" decisions by default as long as you strictly follow its standard.

The explicit aspect of Ecto makes it easier to read and understand the behavior of a piece of code, but it also requires the developer to understand more about the database properties and the features available. What might make Ecto look cumbersome at first glance, is one of its virtues. Based on my personal experience in both ActiveRecord and Ecto world, Ecto's explicitness removes the "behind the scene" effects and uncertainty that is common in projects with ActiveRecord. What a developer reads in code, is what happens in the application and there is no implicit behavior.

In a second blog in a few weeks, in the two part "ActiveRecord vs Ecto" series, we'll cover how queries and validations work in both ActiveRecord and Ecto.

We'd love to know what you thought of this article. We're always on the lookout for new topics to cover, so if you have a subject you'd like to learn more about, please don't hesitate to leave a comment!

_This post is written by guest author [Elvio Vicosa](http://www.elviovicosa.com). Elvio is the author of the book [Phoenix for Rails Developers](http://www.phoenixforrailsdevelopers.com)._