From Zero to Ecto in 10 Minutes
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:
- You have elixir and iex installed (I’m running version 1.2.5; here’s the install guide)
- You have Postgres installed and running (install guide)
- You have a Postgres user named
postgres
set up with a password ofpostgres
- 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:
- The name of our table will be
races
. - We do not specify an
id
field. Ecto provides that automatically. - 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. - 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. timestamps
is provided by Ecto and will create aninserted_at
and anupdated_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!
Buy my book—Phoenix in Action
I've been working hard on the first book on the Phoenix framework from Manning Publications, Phoenix in Action. If you like what you've been reading and/or you have an interest in learning Phoenix, please purchase the book today! Want to know more, check out my blog post announcing the book or the one announcing its completion.