[024] Ecto, Part 1

A brief introduction to using Ecto with Postgres for persistence.

Subscribe now

Ecto, Part 1 [05.13.2016]

Hello again, and welcome to ElixirSips Episode 024: Ecto, Part 1. In today's episode, we're going to cover using Ecto for persistence.

Just a heads up - I had to recompile the latest Elixir in order to run Ecto, as it's meant to run on 11.0 and I was still on 10.4. For whatever reason, I couldn't just do a make clean; make install like I normally would - I had to blow away my elixir directory and clone it again. There was an odd rebar error. Anyway, just a heads up.

Project

Let's go ahead and dive in. Make a new repo:

mix new ecto_test
cd ecto_test

Open up mix.exs and add the following:

  def deps do
    [ { :postgrex, github: "ericmj/postgrex" },
      { :ecto, github: "elixir-lang/ecto" } ]
  end

And fetch the dependencies with mix deps.get. Note: As of this recording, there's a bug in mix that should be fixed tonight. If this works for you, fine. If you get the error you just saw me get, there's an easy fix for it. Just remove ecto from the mix.exs file, run mix deps.get, add it back, and run mix deps.get again. The bug in mix was causing it to not keep track of the order of dependencies in the mix file, and postgrex has to be compiled before ecto will compile.

Repos

Now, it's time to set up a Repo. This is what Ecto uses to actually persist your Entities to the database. It just defines a basic interface into a postgres database. Open up lib/ecto_test/repo.ex:

defmodule EctoTest.Repo do
  use Ecto.Repo, adapter: Ecto.Adapters.Postgres

  def url do
    "ecto://postgres:postgres@localhost/ecto_test"
  end

  def priv do
    app_dir(:ecto_test, "priv/repo")
  end
end

Now, this obviously assumes you're using user postgres, with password postgres, on localhost, and that there is a database that user can access called ecto_test. If that's not the case, go ahead and make the modifications you need to be able to write to the database.

The priv/0 function defines where to place migrations. The value I've provided is the suggested default.

Next, we need to make sure our Repo is started with our application. Our application's Supervisor ought to take care of it, so open up lib/ecto_test/supervisor.ex and make it look like the following:

defmodule EctoTest.Supervisor do
  use Supervisor.Behaviour

  def start_link do
    :supervisor.start_link(__MODULE__, [])
  end

  def init([]) do
    children = [
      worker(EctoTest.Repo, [])
    ]

    # See http://elixir-lang.org/docs/stable/Supervisor.Behaviour.html
    # for other strategies and supported options
    supervise(children, strategy: :one_for_one)
  end
end

Models and Entities

In Ecto, Models and Entities are two distinct concepts, but the preferred strategy is to merge them into one file. An Entity describes the data to be stored in the database. It just defines a record, for the most part, plus some other minor behaviour that's mixed in, such as allowing you to change the default table name used for the entity, and to modify the primary key. If you're building something from scratch, these concerns typically won't come into play.

While the Entity sets up various things that define how to store a record in a database, it is purely data. All behaviour lives in Models, which define how to actually connect an Entity to a database table. There are so far three components in Ecto.Model: Ecto.Model.Queryable, Ecto.Model.Validations, and Ecto.Model.Callbacks. You can find out more in the Ecto documentation, but know that if you use Ecto.Model you bring in all the functionality. You can cherry pick if you want less of it.

With that minor lesson aside, let's go ahead and make our Model. Open up lib/ecto_test/dweet.ex and add the following:

defmodule EctoTest.Dweet do
  use Ecto.Model

  queryable "dweets" do
    field :content,        :string
    field :author,         :string
  end
end

Now we've defined a Model and an Entity, but there's nothing in the database yet to support it. Ecto ships with migration support. To see what mix helpers have been provided by Ecto, run mix help and look for the things prefixed with ecto. We want to generate a migration, so run the following:

mix ecto.gen.migration EctoTest.Repo create_dweets

Now open up the file it created in priv/repo/migrations/20131105131030_create_dweets.exs (obviously this filename will be different because it's a timestamp). Make it look like the following:

defmodule EctoTest.Repo.Migrations.CreateDweet do
  use Ecto.Migration

  def up do
    "CREATE TABLE dweets(id serial primary key, content varchar(140), author varchar(50))"
  end

  def down do
    "DROP TABLE dweets"
  end
end

Now run the migration, with:

mix ecto.migrate EctoTest.Repo

That's all that's required to get started with Ecto. Let's open up an iex with iex -S mix:

iex(1)> d = EctoTest.Dweet.new(content: "foo")
EctoTest.Dweet.Entity[model: EctoTest.Dweet, id: nil, content: "foo",
 author: nil]
iex(2)> d = d.author("jadams")
EctoTest.Dweet.Entity[model: EctoTest.Dweet, id: nil, content: "foo",
 author: "jadams"]
iex(3)> EctoTest.Repo.create(d)

There we go, we've stored our Entity in postgres using Ecto!

Summary

This was a really brief introduction. Ecto also supports relationships, such as has_one and belongs_to and has_many relationships. In the next episode, we'll revisit the dwitter app, changing out its persistence layer to use Ecto and fleshing out the models a bit more. See you soon!