ActiveRecord vs. Ecto Part Two
Tags: elixir, ruby, programming, beginners
Published: 2018-10-09 12:41:16

---
title: ActiveRecord vs. Ecto Part Two
description: The second part of the "ActiveRecord vs. Ecto" series, in which Batman and Batgirl fight over querying databases and we compare apples and oranges.
cover_image: https://blog.appsignal.com/images/blog/2018-10/ar-ecto-2.jpg
tags: elixir, ruby, programming, beginners
published: true
canonical_url: https://blog.appsignal.com/2018/10/09/active-record-vs-ecto-part-2.html
---

This is the second part of the "ActiveRecord vs. Ecto" series, in which Batman and Batgirl fight over querying databases and we compare apples and oranges.

After looking into database schemas and migrations in [ActiveRecord vs. Ecto part one](https://blog.appsignal.com/2018/09/28/active-record-vs-ecto.html), this post covers how both ActiveRecord and Ecto enable developers to query the database, and how both ActiveRecord and Ecto compare when dealing with the same requirements. Along the way, we'll also find out Batgirl's 1989-2011 identity.

## Seed data

Let's get started! Based on the database structure defined in the [first post]([https://blog.appsignal.com/2018/09/28/active-record-vs-ecto.html]) of this series, assume the `users` and the `invoices` tables have the following data stored in them:

__users__

| id | full_name  | email           | created_at* | updated_at          |
|----|------------|-----------------|-----------------------|---------------------|
| 1  | Bette Kane   | bette@kane.test   | 2018-01-01 10:01:00   | 2018-01-01 10:01:00 |
| 2  | Barbara Gordon   | barbara@gordon.test   | 2018-01-02 10:02:00   | 2018-01-02 10:02:00 |
| 3  | Cassandra Cain | cassandra@cain.test | 2018-01-03 10:03:00   | 2018-01-03 10:03:00 |
| 4  | Stephanie Brown | stephanie@brown.test | 2018-01-04 10:04:00   | 2018-01-04 10:04:00 |

\* ActiveRecord's `created_at` field is named `inserted_at` in Ecto by default.

__invoices__

| id | user_id | payment_method | paid_at             | created_at* | updated_at          |
|----|---------|----------------|---------------------|-----------------------|---------------------|
| 1  |       1 | Credit Card    | 2018-02-01 08:00:00 | 2018-01-02 08:00:00   | 2018-01-02 08:00:00 |
| 2  |       2 | Paypal         | 2018-02-01 08:00:00 | 2018-01-03 08:00:00   | 2018-01-03 08:00:00 |
| 3  |       3 |                |                     | 2018-01-04 08:00:00   | 2018-01-04 08:00:00 |
| 4  |       4 |                |                     | 2018-01-05 08:00:00   | 2018-01-05 08:00:00 |

\* ActiveRecord's `created_at` field is named `inserted_at` in Ecto by default.

Queries performed through this post assume that the data above is stored in the database, so keep this information in mind while reading it.

## Find item using its primary key

Let's start with getting a record from the database using its primary key.

### ActiveRecord

```irb
irb(main):001:0> User.find(1)
User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, full_name: "Bette Kane", email: "bette@kane.test", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">
```

### Ecto

```iex
iex(3)> Repo.get(User, 1)
[debug] QUERY OK source="users" db=5.2ms decode=2.5ms queue=0.1ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
%Financex.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "bette@kane.test",
  full_name: "Bette Kane",
  id: 1,
  inserted_at: ~N[2018-01-01 10:01:00.000000],
  invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
  updated_at: ~N[2018-01-01 10:01:00.000000]
}
```

### Comparison

Both cases are quite similar. ActiveRecord relies on the `find` class method of the `User` model class. It means that every ActiveRecord child class has its own `find` method in it.

Ecto uses a different approach, relying on the [Repository](https://martinfowler.com/eaaCatalog/repository.html) concept as a mediator between the mapping layer and the domain. When using Ecto, the `User` module has no knowledge about how to find itself. Such responsibility is present in the `Repo` module, which is able to map it to the underneath datastore, which in our case is Postgres.

When comparing the SQL query itself, we can spot a few differences:

* ActiveRecord loads all the fields (`users.*`), while Ecto loads only the fields listed in the `schema` definition.
* ActiveRecord includes a `LIMIT 1` to the query, while Ecto doesn't.

## Fetching all items

Let's go a step further and load all users from the database.

### ActiveRecord

```irb
irb(main):001:0> User.all
User Load (0.5ms)  SELECT  "users".* FROM "users" LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, full_name: "Bette Kane", email: "bette@kane.test", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">, #<User id: 2, full_name: "Barbara Gordon", email: "barbara@gordon.test", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">, #<User id: 3, full_name: "Cassandra Cain", email: "cassandra@cain.test", created_at: "2018-01-03 10:03:00", updated_at: "2018-01-03 10:03:00">, #<User id: 4, full_name: "Stephanie Brown", email: "stephanie@brown.test", created_at: "2018-01-04 10:04:00", updated_at: "2018-01-04 10:04:00">]>
```

### Ecto

```iex
iex(4)> Repo.all(User)
[debug] QUERY OK source="users" db=2.8ms decode=0.2ms queue=0.2ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
[
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "bette@kane.test",
    full_name: "Bette Kane",
    id: 1,
    inserted_at: ~N[2018-01-01 10:01:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-01 10:01:00.000000]
  },
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "barbara@gordon.test",
    full_name: "Barbara Gordon",
    id: 2,
    inserted_at: ~N[2018-01-02 10:02:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-02 10:02:00.000000]
  },
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "cassandra@cain.test",
    full_name: "Cassandra Cain",
    id: 3,
    inserted_at: ~N[2018-01-03 10:03:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-03 10:03:00.000000]
  },
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "stephanie@brown.test",
    full_name: "Stephanie Brown",
    id: 4,
    inserted_at: ~N[2018-01-04 10:04:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-04 10:04:00.000000]
  }
]
```

### Comparison

It follows the exact same pattern as the previous section. ActiveRecord uses the `all` class method and Ecto relies on the repository pattern to load the records.

There are again some differences in the SQL queries:

* The same as the previous section, ActiveRecord loads all the fields (`users.*`), while Ecto loads only the fields listed in the `schema` definition.
* ActiveRecord also defines a `LIMIT 11`, while Ecto simply loads everything. This limit comes from the `inspect` method used on the console (https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L599).

## Querying with conditions

It's very unlikely that we need to fetch all the records from a table. A common need is the use of conditions, to filter out the data returned.

Let's use that example to list all the `invoices` which are still to be paid (`WHERE paid_at IS NULL`).

### ActiveRecord

```irb
irb(main):024:0> Invoice.where(paid_at: nil)
Invoice Load (18.2ms)  SELECT  "invoices".* FROM "invoices" WHERE "invoices"."paid_at" IS NULL LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 3, user_id: 3, payment_method: nil, paid_at: nil, created_at: "2018-01-04 08:00:00", updated_at: "2018-01-04 08:00:00">, #<Invoice id: 4, user_id: 4, payment_method: nil, paid_at: nil, created_at: "2018-01-05 08:00:00", updated_at: "2018-01-05 08:00:00">]>
```

### Ecto

```iex
iex(19)> where(Invoice, [i], is_nil(i.paid_at)) |> Repo.all()
[debug] QUERY OK source="invoices" db=20.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 WHERE (i0."paid_at" IS NULL) []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 3,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 3
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 4,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 4
  }
]
```

### Comparison

In both examples, the `where` keyword is used, which is a connection to the SQL `WHERE` clause. Although the generated SQL queries are quite similar, the way how both tools get there have some important differences.

ActiveRecord transforms the `paid_at: nil` argument to the `paid_at IS NULL` SQL statement automatically. In order to get to the same output using Ecto, developers need to be more explicit about their intent, by calling the `is_nil()`.

Another difference to be highlighted is the "pure" behaviour of the function `where` in Ecto. When calling the `where` function alone, it doesn't not interact with the database. The return of the `where` function is a `Ecto.Query` struct:

```iex
iex(20)> where(Invoice, [i], is_nil(i.paid_at))
#Ecto.Query<from i in Financex.Accounts.Invoice, where: is_nil(i.paid_at)>
```

The database is only touched when the `Repo.all()` function is called, passing the `Ecto.Query` struct as argument. This approach allows query composition in Ecto, which is the subject of the next section.

## Query composition

One of the most powerful aspects of database queries is composition. It describing a query in a way that contains more than a single condition.

If you are building raw SQL queries, it means you'll probably use some kind of concatenation. Imagine you have two conditions:

1. `not_paid = 'paid_at IS NOT NULL'`
2. `paid_with_paypal = 'payment_method = "Paypal"'`

In order to combine those two conditions using raw SQL, means you'll have to concatenate them using something similar to:

    SELECT * FROM invoices WHERE #{not_paid} AND #{paid_with_paypal}

Luckily both ActiveRecord and Ecto have a solution for that.

### ActiveRecord

```irb
irb(main):003:0> Invoice.where.not(paid_at: nil).where(payment_method: "Paypal")
Invoice Load (8.0ms)  SELECT  "invoices".* FROM "invoices" WHERE "invoices"."paid_at" IS NOT NULL AND "invoices"."payment_method" = $1 LIMIT $2  [["payment_method", "Paypal"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>
```

### Ecto

```iex
iex(6)> Invoice |> where([i], not is_nil(i.paid_at)) |> where([i], i.payment_method == "Paypal") |> Repo.all()
[debug] QUERY OK source="invoices" db=30.0ms decode=0.6ms queue=0.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 WHERE (NOT (i0."paid_at" IS NULL)) AND (i0."payment_method" = 'Paypal') []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  }
]
```

### Comparison

Both queries are answering the same question: "Which invoices were paid and used Paypal?".

As already expected, ActiveRecord offers a more succinct way of composing the query (for that example), while Ecto requires developers to spend a bit more on writing the query. As usual, Batgirl (the Orphan, mute one with the Cassandra Cain identity) or Activerecord is not as verbose.

Don't be fooled by the verbosity and apparent complexity of the Ecto query shown above. In a real world environment, that query would be rewritten to look more like:

```elixir
Invoice
|> where([i], not is_nil(i.paid_at))
|> where([i], i.payment_method == "Paypal")
|> Repo.all()
```

Seeing from that angle, the combination of the "pure" aspects of the function `where`, which does not perform database operations by itself, with the pipe operator, makes query composition in Ecto really clean.

## Ordering

Ordering is an important aspect of a query. It enables developers to ensure that a given query result follows a specified order.

### ActiveRecord

```irb
irb(main):002:0> Invoice.order(created_at: :desc)
Invoice Load (1.5ms)  SELECT  "invoices".* FROM "invoices" ORDER BY "invoices"."created_at" DESC LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 4, user_id: 4, payment_method: nil, paid_at: nil, created_at: "2018-01-05 08:00:00", updated_at: "2018-01-05 08:00:00">, #<Invoice id: 3, user_id: 3, payment_method: nil, paid_at: nil, created_at: "2018-01-04 08:00:00", updated_at: "2018-01-04 08:00:00">, #<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">, #<Invoice id: 1, user_id: 1, payment_method: "Credit Card", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-02 08:00:00", updated_at: "2018-01-02 08:00:00">]>
```

### Ecto

```iex
iex(6)> order_by(Invoice, desc: :inserted_at) |> Repo.all()
[debug] QUERY OK source="invoices" db=19.8ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 ORDER BY i0."inserted_at" DESC []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 3,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 3
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 4,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 4
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 1,
    inserted_at: ~N[2018-01-02 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Credit Card",
    updated_at: ~N[2018-01-02 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  }
]
```

### Comparison

Adding order to a query is straight-forward in both tools.

Although the Ecto example uses a `Invoice` as first parameter, the `order_by` function also accepts `Ecto.Query` structs, which enables the `order_by` function to be used in compositions, like:

```elixir
Invoice
|> where([i], not is_nil(i.paid_at))
|> where([i], i.payment_method == "Paypal")
|> order_by(desc: :inserted_at)
|> Repo.all()
```

## Limiting

What would be a database without limit? A disaster. Luckily, both ActiveRecord and Ecto help to limit the number of returned records.

### ActiveRecord

```irb
irb(main):004:0> Invoice.limit(2)
Invoice Load (0.2ms)  SELECT  "invoices".* FROM "invoices" LIMIT $1  [["LIMIT", 2]]
=> #<ActiveRecord::Relation [#<Invoice id: 1, user_id: 1, payment_method: "Credit Card", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-02 08:00:00", updated_at: "2018-01-02 08:00:00">, #<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>
```

### Ecto

```iex
iex(22)> limit(Invoice, 2) |> Repo.all()
[debug] QUERY OK source="invoices" db=3.6ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 LIMIT 2 []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 1,
    inserted_at: ~N[2018-01-02 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Credit Card",
    updated_at: ~N[2018-01-02 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  }
]
```

### Comparison

Both ActiveRecord and Ecto have a way of limiting the number of records returned by a query.

Ecto's `limit` works similarly to `order_by`, being suitable for query compositions.

## Associations

ActiveRecord and Ecto have different approaches when it comes to how associations are handled.

### ActiveRecord

In ActiveRecord, you can use any association defined in a model, without having to do anything special about that, for example:

```irb
irb(main):012:0> user = User.find(2)
User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, full_name: "Barbara Gordon", email: "barbara@gordon.test", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">
irb(main):013:0> user.invoices
Invoice Load (0.4ms)  SELECT  "invoices".* FROM "invoices" WHERE "invoices"."user_id" = $1 LIMIT $2  [["user_id", 2], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>
```

The example above shows that we can get a list of the user invoices when calling `user.invoices`. When doing so, ActiveRecord automatically queried the database and loaded the invoices that are associated with the user. While this approach makes things easier, in the sense of writing less code or having to worry about extra steps, it might be a problem if you are iterating over a number of users and fetching the invoices for each user. This issue is known as the "N + 1 problem".

In ActiveRecord, the proposed fix to the "N + 1 problem" is to use the `includes` method:

```irb
irb(main):022:0> user = User.includes(:invoices).find(2)
User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
Invoice Load (0.6ms)  SELECT "invoices".* FROM "invoices" WHERE "invoices"."user_id" = $1  [["user_id", 2]]
=> #<User id: 2, full_name: "Barbara Gordon", email: "barbara@gordon.test", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">
irb(main):023:0> user.invoices
=> #<ActiveRecord::Associations::CollectionProxy [#<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>
```

In this case, ActiveRecord eager-loads the `invoices` association when fetching the user (as seen in the two SQL queries shown).

### Ecto

As you might already have noticed, Ecto really doesn't like magic or implicitness. It requires developers to be explicit about their intents.

Let's try the same approach of using `user.invoices` with Ecto:

```iex
iex(7)> user = Repo.get(User, 2)
[debug] QUERY OK source="users" db=18.3ms decode=0.6ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
%Financex.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "barbara@gordon.test",
  full_name: "Barbara Gordon",
  id: 2,
  inserted_at: ~N[2018-01-02 10:02:00.000000],
  invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
  updated_at: ~N[2018-01-02 10:02:00.000000]
}
iex(8)> user.invoices
#Ecto.Association.NotLoaded<association :invoices is not loaded>
```

The result is a `Ecto.Association.NotLoaded`. Not so useful.

To have access to the invoices, a developer needs to let Ecto know about that, using the `preload` function:

```iex
iex(12)> user = preload(User, :invoices) |> Repo.get(2)
[debug] QUERY OK source="users" db=11.8ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
[debug] QUERY OK source="invoices" db=4.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at", i0."user_id" FROM "invoices" AS i0 WHERE (i0."user_id" = $1) ORDER BY i0."user_id" [2]
%Financex.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "barbara@gordon.test",
  full_name: "Barbara Gordon",
  id: 2,
  inserted_at: ~N[2018-01-02 10:02:00.000000],
  invoices: [
    %Financex.Accounts.Invoice{
      __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
      id: 2,
      inserted_at: ~N[2018-01-03 08:00:00.000000],
      paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
      payment_method: "Paypal",
      updated_at: ~N[2018-01-03 08:00:00.000000],
      user: #Ecto.Association.NotLoaded<association :user is not loaded>,
      user_id: 2
    }
  ],
  updated_at: ~N[2018-01-02 10:02:00.000000]
}

iex(15)> user.invoices
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  }
]
```

Similarly to ActiveRecord `includes`, the preload with fetch the associated `invoices`, which will make them available when calling `user.invoices`.

### Comparison

Once again, the battle between ActiveRecord and Ecto ends up with a known-point: explicitness. Both tools enable developers to easily access associations, but while ActiveRecord makes it less verbose, the result of it might have unexpected behaviours. Ecto follows the WYSIWYG kind of approach, which only does what is seen in the query defined by the developer.

Rails is well-known for using and promoting caching strategies to all the different layers of the application. One example is about using the "Russian doll" caching approach, which relies entirely on the "N + 1 problem" for its caching mechanism to perform its magic.

## Validations

Most validations present in ActiveRecord are also available in Ecto. Here's a list of common validations and how both ActiveRecord and Ecto define them:

| ActiveRecord                                   | Ecto                                                  |
|-------------------------------------------------|-------------------------------------------------------|
| `validates :title, presence: true`              | `validate_required(changeset, [:title])`              |
| `validates :email, confirmation: true`          | `validate_confirmation(changeset, :email)`            |
| `validates :email, format: {with: /@/`}         | `validate_format(changeset, :email, ~r/@/)`           |
| `validates :start, exclusion: {in: %w(a b)}`    | `validate_exclusion(changeset, :start, ~w(a b))`      |
| `validates :start, inclusion: {in: %w(a b)}`    | `validate_inclusion(changeset, :start, ~w(a b))`      |
| `validates :terms_of_service, acceptance: true` | `validate_acceptance(changeset, :terms_of_service)`   |
| `validates :password, length: {is: 6}`          | `validate_length(changeset, :password, is: 6)`        |
| `validates :age, numericality: {equal_to: 1}`   | `validate_number(changeset, :age, equal_to: 1)`       |


## Wrap up

There you have it: the essential apples versus oranges comparison.

ActiveRecord focuses on the ease of performing database queries. The great majority of its features are concentrated on the model classes themselves, not requiring developers to have a deep understanding of the database, nor the impact of such operations. ActiveRecord does lots of things implicitly by default. Although that makes it easier to get started, it makes it harder to understand what is happening behind the scenes and it only works if you follow the "ActiveRecord way".

Ecto, on the other hand, requires explicitness which results in more verbose code. As a benefit, everything is in the spotlight, nothing behind the scenes, and you can specify your own way.

Both have their upside depending on your perspective and preference. So having compared apples and oranges, we come to the end of this BAT-tle. Almost forgot to tell you BatGirl's codename (1989 - 2001) was .... Oracle. But let's not go into that. 😉

_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)._