Ectogram: Setting Up Ecto
6 min readI am assuming if you are following along with this series that you have elixir
, erlang
, & psql
installed on your machine. Please refer back to the intro post for the versions this post is following. With that let’s dive in!
Adding & Configuring Ecto
The first thing we will need to do is create an elixir project:
mix new ectogram --sup
cd ectogram
This is a template of an elixir project. It has no dependencies present at all so to work with Ecto we will need to install it using mix
. First open your mix.exs
file and add the following, then run the below command:
defp deps do
[
{:ecto_sql, "~> 3.0"},
{:postgrex, ">= 0.0.0"}
]
end
Then we can install the dependencies by running:
mix deps.get
With Ecto installed we now have access to the Ecto command line tools that have been added to mix
. To generate Ectogram’s Repo we will run:
mix ecto.gen.repo -r Ectogram.Repo
We will also create environment based configs that we will need later for testing:
touch config/{dev,test}.exs
Following the instructions output by the command we will update the application.ex
and the config.exs
:
def start(_type, _args) do
# ...
children = [
{Ectogram.Repo, []}
]
# ...
end
import Config
config :ectogram,
ecto_repos: [Ectogram.Repo]
import_config "#{config_env()}.exs"
import Config
config :ectogram, Ectogram.Repo,
database: "ectogram_dev",
username: "postgres", # Make sure you change to credentials on your machine!
password: "postgres", # Make sure you change to credentials on your machine!
hostname: "localhost"
import Config
config :ectogram, Ectogram.Repo,
username: "postgres", # Make sure you change to credentials on your machine!
password: "postgres", # Make sure you change to credentials on your machine!
hostname: "localhost",
database: "ectogram_test#{System.get_env("MIX_TEST_PARTITION")}",
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: 10
One last thing we need to do before creating the Ectogram database is to update our mix.exs
config to be able to reach for the correct environment config at runtime:
defmodule Ectogram.MixProject do
use Mix.Project
def project do
[
app: :ectogram,
version: "0.1.0",
elixir: "~> 1.13",
+ elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Ectogram.Application, []}
]
end
+ defp elixirc_paths(:test), do: ["lib", "test/support"]
+ defp elixirc_paths(_), do: ["lib"]
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:ecto_sql, "~> 3.0"},
{:postgrex, ">= 0.0.0"}
]
end
end
After making these configuration changes we can run the below to create the ectogram_dev
database in Postgres.
mix ecto.create
And to verify that the database is infact on our machine we can run:
psql ectogram_dev
ectogram_dev=#
Customizing Ecto
There are some customizations I want to make before proceeding onward to creating tables and schemas. The following can be done on a table-by-table and schema-by-schema basis; however the changes I want to make I want applied across all tables and schemas so applying these at the top level of Ectogram’s configuration makes more sense. The first customization will be to change the defaults of Ecto.Migration. In our config.exs
we will make another entry:
import Config
+config :ectogram, Ectogram.Repo,
+ migration_foreign_key: [column: :id, type: :binary_id],
+ migration_primary_key: [name: :id, type: :binary_id],
+ migration_timestamps: [inserted_at: :created_at, type: :utc_datetime_usec, updated_at: :modified_at]
config :ectogram,
ecto_repos: [Ectogram.Repo]
import_config "#{config_env()}.exs"
The above changes do the following when we run a migration:
- All foreign keys by default are
:bigserial
and we are changing that to be:binary_id
or UUIDs. - All primary keys are also by default
:bigserial
and again we are changing that to be in a UUID format. - We are renaming the default timestamps of
inserted_at
andupdated_at
tocreated_at
andmodified_at
. Call me crazy I just like those better. The default type for timestamps is:naive_datetime
and I prefer to set it to:utc_datetime_usec
.
Next we will create a custom schema for Ectogram to override defaults of Ecto.Schema. This will make sure that our schema is inline with our tables so when we preform a changeset we don’t get told things like “inserted_at
does not exist did you mean created_at
”.
touch /lib/ectogram/schema.ex
defmodule Ectogram.Schema do
@moduledoc """
Customizes the properties of Ecto.Schema.
Now instead of:
use Ecto.Schema
do:
use Ectogram.Schema
"""
defmacro __using__(_) do
quote do
use Ecto.Schema
@foreign_key_type :binary_id
@primary_key {:id, :binary_id, autogenerate: true}
@timestamps_opts [inserted_at: :created_at, type: :utc_datetime_usec, updated_at: :modified_at]
end
end
end
Prepping The Test Suite
mkdir test/support && touch test/support/data_case.ex
The Ectogram.DataCase
is an ExUnit.CaseTemplate
that we can use in our tests that essentially bootstrap the testing environment. In our case we need to have access to a database to run our tests so a sandbox is spun up. Every test will create a transaction and on the completion of that test the transaction will be rolled back automatically preventing their being stale data that could effect other tests. You can read more about it in the guide on Phoenix’s website.
defmodule Ectogram.DataCase do
use ExUnit.CaseTemplate
using do
quote do
alias Ectogram.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Ectogram.DataCase
end
end
setup tags do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Ectogram.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
:ok
end
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
end
end
ExUnit.start()
+ Ecto.Adapters.SQL.Sandbox.mode(Ectogram.Repo, :manual)
Final Housekeeping
The last few things we will do before getting started is to add aliases to the project for faster command line magic, add a seeds.exs
file for the future, and a super magical .iex.exs
!
touch priv/repo/seeds.ex .iex.exs
The .iex.exs
will be loaded by iex
when we fire it up. This will make our lives easier later on when we are using iex
because we can have our modules already aliased and ready to be accessed faster!
alias Ectogram.{Repo}
import_if_available Ecto.Query
import_if_available Ecto.Changeset
And the final thing to do are to add some nice aliases for setting up and tearing down Ectogram’s database. You will notice their is even a call to the seeds.exs
file we created. It won’t do anything yet, but in the next post we will begin adding seeds as we build out the data structures for the user.
defmodule Ectogram.MixProject do
use Mix.Project
def project do
[
app: :ectogram,
version: "0.1.0",
elixir: "~> 1.13",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
- deps: deps()
+ deps: deps(),
+ aliases: aliases()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Ectogram.Application, []}
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:ecto_sql, "~> 3.0"},
{:postgrex, ">= 0.0.0"}
]
end
+ defp aliases do
+ [
+ setup: ["deps.get", "ecto.setup"],
+ "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
+ "ecto.reset": ["ecto.drop", "ecto.setup"],
+ test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
+ ]
+ end
end
And with that we are ready to forge ahead into building out the first data structure and table in Ectogram, our user! You can find all the code pertaining to this post in PR#1.
Related Articles
Ectogram: Testing Ecto
Part 4 in the Ectogram series where I cut my teeth on testing the user schema with ExUnit.
Ectogram: Generating The Users Table
Part 3 in the Ectogram series where I generate the user table migration and schema.
Ectogram: Introduction
A clone of the popular social media platform, Instagram, written in Elixir & Ecto.
Cody is a Christian, USN Veteran, Jayhawk, and an American expat living outside of Bogotá, Colombia. He is currently looking for new opportunities in the tech industry.