From Zero to Ecto in 10 Minutes

May 30, 2016 9 minutes read

In my last post, Using Ecto For Formula 1 Standings, I showed you how you can compose Ecto Queries — building them up as you go along. The examples used were taken from a fantasy Formula 1 web application I am building in Elixir and the Phoenix framework. After getting some feedback and discussing the topic with some fellow developers, I discovered that there wasn’t much knowledge about how to use Ecto outside of Phoenix.

So let’s set up a brand new Elixir project and bring in Ecto without Phoenix.

I’m currently recording a series of screencasts that creates an Elixir application with Ecto. If you’d like to be notified when I release them, please sign up at the bottom of this post.

Building Up A Database Of Formula 1 Races

In our little application, we are going to try to do as little as possible. Just like a true developer, huh? By the end, our success will only be measured in our ability to connect to a local Postgres database and create new records in that database in an iex session.

Not a fan of Formula 1? This example is completely contrived so feel free to edit the code however you’d like as we go along. If you run into any trouble, feel free to ask me for help on Twitter at @geolessel.

Qualifying Session

There are a few assumptions I’m going to be making during this post:

  1. You have elixir and iex installed (I’m running version 1.2.5; here’s the install guide)
  2. You have Postgres installed and running (install guide)
  3. You have a Postgres user named postgres set up with a password of postgres
  4. The postgres user has permissions to create and modify a database

I unfortunately don’t have the ability to go into the installation of all these in this post but there are plenty of resources online that can help you install all this on your particular development environment.

Start Your Engines!

As with most new Elixir applications, we are going to start ours with mix new. I’m going to be calling my app Racebook.

> mix new racebook --sup

If you’ve used Elixir before, this should look very familiar. If you haven’t used Elixir much before, this basically creates a basic outline of an application for us. However, unless you’ve progressed past the initial learning phases of Elixir, you might not have seen the --sup option. What is that? Did you know you can ask mix itself?

> mix help new

A --sup option can be given to generate an OTP application skeleton including a supervision tree. Normally an app is generated without a supervisor and without the app callback.

In our case, we need to create an OTP application and supervisor to keep the database connection alive for us to use when we need it. We could write all the code by hand if we wanted but why not take advantage of mix’s kindness and have it generate the skeleton we need? Let’s be lazy!

Not familiar with OTP? Here’s how the Elixir site describes it (you can go there to learn more):

OTP (Open Telecom Platform) is a set of libraries that ships with Erlang. Erlang developers use OTP to build robust, fault-tolerant applications.

Now that we have a skeleton created, let’s get to work. cd into the directory mix new created and open up your favorite editor.

mix.exs

The first file we want to edit is the mix.exs file. This file tells mix about our project and what it needs in order to function. First, let’s edit the deps function.

defp deps do
  [
    {:postgrex, ">= 0.0.0"},
    {:ecto, "~> 2.0.0-beta"}
  ]
end

This function defines our application’s dependencies. postgrex is a PostgreSQL driver that Ecto needs in order to talk to the database. We’re not too concerned with the versions here so let’s just get the latest version of postgrex and the 2.0.0 beta version of ecto.

The next thing we need to do is make sure that these two dependencies are started up with our application. Let’s edit the application function and add these two dependencies.

def application do
  [applications: [:logger, :postgrex, :ecto],
   mod: {Racebook, []}]
end

Now that we’ve added those as dependencies, we can tell mix to go get those dependencies and add them to our project. In your terminal, run:

mix deps.get

config/config.exs

Back in our editor, open up config/config.exs. As you may have guessed based on the name, this is where we put in our configurations. We could get real fancy here with environments and such, but let’s keep it dead simple. Add the following to the bottom of the file:

config :racebook, ecto_repos: [Racebook.Repo]

config :racebook, Racebook.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "racebook_dev",
  username: "postgres",
  password: "postgres",
  hostname: "localhost"

The first line is telling racebook that we’d like to “register” an Ecto Repo for our application. A Repo is basically a way to connect Ecto itself to a database. Where did we get the name Racebook.Repo from? We made it up! Don’t worry about it for right now — we will actually define the module in just a bit.

The second line actually configures the Repo itself telling Ecto things like which adapter to use and our database credentials. For database, you can again just make up whatever sounds good to you.

lib/repo.ex

Now let’s define that Racebook.Repo module we told Ecto about. It’s going to be a pretty simple file that does quite a bit. Create a new file named lib/repo.ex:

defmodule Racebook.Repo do
  use Ecto.Repo, otp_app: :racebook
end

That’s it! We are just bringing in Ecto.Repo and all its functions and telling it the name of our application.

lib/racebook.ex

There’s one last thing we need to do setup-wise to get Ecto going — we need to ensure that we’ve got a supervisor monitoring our Repo to ensure that we’ve got a good consistent connection to our database. In the lib/cabinet.ex file, add a supervisor in the children list.

children = [
  supervisor(Racebook.Repo, [])
]

lib/race.ex

We’re finally able to get to the point of defining modules specific to our application. Everything up until now are things that you’ll likely do in any application in which you’d like to use Ecto. It doesn’t take a long time to set up, but it obviously touches a handful of files.

In our application, we are going to have a structure containing information about a Race. As a reminder, we are not going to get really detailed here, but instead do a minimal amount to get us up and running. All we will track in our database is the race name and our rating of the race on a 1-10 scale. Create a lib/race.ex file.

defmodule Racebook.Race do
  use Ecto.Schema

  schema "races" do
    field :name, :string
    field :rating, :integer, default: 6
    timestamps
  end
end

We first tell our module that we will be using the Ecto.Schema module which gives us the schema and field macros (and much more). A couple notes:

  1. The name of our table will be races.
  2. We do not specify an id field. Ecto provides that automatically.
  3. We specify that our name field is a :string. :string is the default so we coule have left that off if we wanted to. I, however, like to be explicit.
  4. Our rating field is an :integer and has a default of 6. We can set defaults here in our schema to make things easier for us.
  5. timestamps is provided by Ecto and will create an inserted_at and an updated_at column in our table for us.

Finally we have a schema defined that is the basis of our application data. We only have one more thing to do in order to start inserting our data.

Creating the Migration

Even though we have defined the schema that Ecto will use to interact with our database, the database itself and the races table are not yet created. In order to do that, we need to run a few mix commands from the terminal and create one more file.

> mix ecto.gen.migration create_races

This will build a skeleton migration for us in which we can actually tell our database about the table we want to create. create_races is the name of the migration. It can be named anything we want but it is nice to be descriptive of what we are doing in the migration. The file it actually creates will be prepended by the date and time of when the command was run. For me, it is 20160530137628_create_races.exs. Open up the created file.

def change do
  create table(:races) do
    add :name, :string
    add :rating, :integer, default: 6
    timestamps
  end
end

If you are coming from Ruby and are familiar with ActiveRecord migrations, you should feel right at home. Ecto provides a change function that it knows how to rollback. Alternatively, we can specify exactly what we want to happen forward and backwards by defining an up and down function.

In here we create a table named races and add a few columns that match up with the schema we defined above (including timestamps).

Heading back out to the terminal, run these commands:

> mix ecto.create
# creates our database

> mix ecto.migrate
# runs the migration we just created

This creates our database (we only need to do this once per project) and runs any migrations it hasn’t yet run (needed every time we make a change to the database through a migration). If we made a mistake and ever needed to reverse the last migration, we could run mix ecto.rollback.

Let’s Create Some Database Records

That’s all we need to do in order to get Ecto in your application! Now that it is all set up, let’s use it to enter a race into the database. Start up a mix session using our application.

> iex -S mix

Once you are in the iex session, let’s just do a check to see that everything is hooked up correctly and our application can successfully connect to the database:

iex> Racebook.Repo.all Racebook.Race

[]

We are asking Ecto to get all the races in our database. There haven’t been any records inserted yet, so we know this should return an empty list. But it is a good uncomplicated test to see if we have everything connected properly.

Next, let’s create a record:

iex> %Racebook.Race{name: "2016 Monaco Grand Prix", rating: 8} |>
...> Racebook.Repo.insert

# {:ok,
#  %Racebook.Race{__meta__: #Ecto.Schema.Metadata<:loaded>, id: 1,
#  inserted_at: #Ecto.DateTime<2016-05-30 21:01:32>, name: "2016 Monaco Grand Prix",
#  rating: 8, updated_at: #Ecto.DateTime<2016-05-30 21:01:32>}}

Even though we didn’t define a Struct for our Race module, Ecto took care of that for us. The schema we provided it is how it defined the structure. If your command was run correctly, you should see some debug information and then a return in the form of {:ok, record}.

And with that done successfully, we have completed our goal for this article! Nice job!

Wrapping it up

As a summary, we touched the following files:

  • mix.exs
  • config/config.exs
  • lib/repo.ex
  • lib/racebook.ex
  • lib/race.ex
  • 20160530137628_create_races.exs

As always, I’d love to hear your feedback. You can contact me on Twitter at @geolessel or in the Elixir Slack group at @geo.

I mentioned this earlier in the article, but I am in the process of creating a video series aimed at creating a small application using Ecto in which I go deeper into Ecto and how to use it effectively. If you’d like to be notified when that is complete and posted, sign up below. Thanks for reading!

Updated: