Database wrapper and query generator for elixir.
Ecto.Repo
— where the data isEcto.Schema
— what the data isEcto.Query
— how to read the dataEcto.Changeset
— how to change the datamix ecto.gen.repo -r Friends.Repo
config/config.exs
config :friends, Friends.Repo,
database: "friends",
username: "user",
password: "pass",
hostname: "localhost"
config :friends, ecto_repos: [Friends.Repo] # Define repo to use in ecto
lib/friends/repo.ex
defmodule Friends.Repo do
use Ecto.Repo,
otp_app: :friends,
adapter: Ecto.Adapters.Postgres
end
Friends.Repo
in the app supervision tree in lib/friends/application.ex
mix ecto.create
mix ecto.gen.migration create_people
change
method in priv/repo/migrations/{generated_create_people_migration_file}
create table(:people) do
add :first_name, :string
add :last_name, :string
add :age, :integer
end
mix ecto.migrate
defmodule Friends.Person do
use Ecto.Schema
schema "people" do
field :first_name, :string
field :last_name, :string
field :age, :integer
end
end
person = %Friends.Person{}
person = %{person | age: 28}
person.age
{:ok, person} = Friends.Repo.insert person
Friends.Repo.insert changeset
Ecto.Changeset.cast(person, params, [:first_name, :last_name, :age])
Ecto.Changeset.validate_required([:first_name])
changeset.valid?
changeset.errors
[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]}]
Ecto.Changeset.traverse_errors/2
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)"],
}
from/2
# `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
Single record
Repo.get(Movie, 1)
Repo.get(Movie, title: "Ready Player One")
Movie |> Ecto.Query.first() |> Repo.one()
Movie |> Ecto.Query.last() |> Repo.one()
Repo.get!(Movie, 1)
Multiple records
Movie |> Repo.all()
Movie |> Repo.stream() |> Enum.each(fn r -> ... end)
Movie |> Repo.exists?()
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
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)
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])
Single record
person = Repo.get!(Person, 1)
Repo.delete(person)
Multiple records
Repo.delete_all(Person)