Database wrapper and query generator for elixir.

The 4 Components

Setting up a Repo

mix ecto.gen.repo -r Friends.Repo
config :friends, Friends.Repo,
  database: "friends",
  username: "user",
  password: "pass",
  hostname: "localhost"
  
config :friends, ecto_repos: [Friends.Repo] # Define repo to use in ecto
defmodule Friends.Repo do
  use Ecto.Repo,
    otp_app: :friends,
    adapter: Ecto.Adapters.Postgres
end

Setting up the database

create table(:people) do
  add :first_name, :string
  add :last_name, :string
  add :age, :integer
end

Schema

defmodule Friends.Person do
  use Ecto.Schema

  schema "people" do
    field :first_name, :string
    field :last_name, :string
    field :age, :integer
  end
end

Repo

Changeset

[first_name: {"can't be blank", [validation: :required]},
 last_name: {"can't be blank", [validation: :required]},
 bio: {"should be at least %{count} character(s)", [count: 15, validation: :length, kind: :min, type: :string]}]
traverse_errors(changeset, fn {msg, opts} ->
  Enum.reduce(opts, msg, fn {key, value}, acc ->
    String.replace(acc, "%{#{key}}", to_string(value))
  end)
end)
%{
  first_name: ["can't be blank"],
  last_name: ["can't be blank"],
  bio: ["should be at least 15 character(s)"],
}

Query

# `in` expression
from(c in City, select: c)

# Ecto.Queryable
from(City, limit: 1)

# Fragment with user-defined function and predefined columns
from(f in fragment("my_table_valued_function(arg)"), select: f.x)

# Fragment with built-in function and undefined columns
from(f in fragment("select generate_series(?::integer, ?::integer) as x", ^0, ^10), select: f.x)
def with_minimum(age, height_ft) do
  from u in "users",
    where: u.age > type(^age, :integer) and u.height > ^(height_ft * 3.28),
    select: u.name
end

with_minimum(18, 5.0)
from u in User, where: is_nil(u.age)
from u in (from u in User, where: u.age > 18), select: u.name

Other concepts

Resources

CRUD

Fetching records

Single record

Multiple records

Querying records

Keyword-based queries

query =
	from Movie,
	where: [title: "Ready Player One"],
	select: [:title, :tagline]
Repo.all(query)
query =
	from m in Movie,
	where: m.title == "Ready Player One"
	select: [m.title, m.tagline]
Repo.all(query)

Interpolation with ^

title = "Ready Player One"
query =
	from m in Movie,
	where: m.title == ^title,
	select: [m.title, m.tagline]
Repo.all(query)

Pipe-based queries

Movie
|> where([m], m.title == "Ready Player One")
|> select([m], {m.title, m.tagline})
|> Repo.all

Inserting records

Single record

%Person{name: "Bob"}
|> Repo.insert()
params = %{"name"=>"Bob"}

%Person{}
|> Ecto.Changeset.cast(params, [:name])
|> Repo.insert()

Multiple records

data = [%{name: "Bob"}, %{name: "Alice"}]
Repo.insert_all(Person, data)

Updating records

Single record

person =
	Person
	|> Ecto.Query.first()
	|> Repo.one!()
	
changeset = change(person, %{age: 29})
Repo.update(changeset)
params = %{"age" => "29"}

person =
	Person
	|> Ecto.Query.First()
	|> Repo.one!()
	
changeset = cast(person, params, [:age])
Repo.update(changeset)

Multiple records

Repo.update_all(Person, set: [age: 29])

Deleting records

Single record

person = Repo.get!(Person, 1)
Repo.delete(person)

Multiple records

Repo.delete_all(Person)

Associations