Ecto
Ecto is an ORM, or more precisely a "language integrated query" (LINQ) library for Elixir.
Schema
defmodule App1.User do
use Ecto.Schema
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
timestamps() # Adds :inserted_at and :updated_at fields
end
end
Changeset
Change set is a way to track and validate changes to data before persisting them.
castselects the allowed fields from input.validate_*functions add errors if rules are broken.changesetfunction is usually the place to define these rules, you may use separated functions for insert and update or other purpose.
defmodule App1.User do
use Ecto.Schema
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
timestamps() # Adds :inserted_at and :updated_at fields
end
def changeset(user, attrs) do
user
|> Ecto.Changeset.cast(attrs, [:name, :email, :age])
|> Ecto.Changeset.validate_required([:name, :email])
|> Ecto.Changeset.validate_number(:age, greater_than_or_equal_to: 0)
|> Ecto.Changeset.unique_constraint(:email, name: :users_email_index)
end
end
Create a Changeset:
alias App1.User
%User{}
|> User.changeset(%{name: "Alice"})
#Ecto.Changeset<
action: nil,
changes: %{name: "Alice"},
errors: [email: {"can't be blank", [validation: :required]}],
data: #App1.User<>,
valid?: false,
...
>
changeset.valid? is false and changeset.errors contains the validation messages. Typically these errors will returned to the user (e.g., in a web form).
Fix the Changeset:
%User{}
|> User.changeset(%{name: "Alice", email: "alice@example.com", age: 30})
#Ecto.Changeset<
action: nil,
changes: %{name: "Alice", email: "alice@example.com", age: 30},
errors: [],
data: #App1.User<>,
valid?: true,
...
>
Now changeset.valid? is true and changeset.errors is empty. And this changeset can be inserted into the database using Repo.insert(changeset).
Repo
A repository module is needed to interact with the database.
defmodule App1.Repo do
use Ecto.Repo,
otp_app: :app1,
adapter: Ecto.Adapters.Postgres
end
In config/config.exs:
config :app1, App1.Repo,
url: System.get_env("DATABASE_URL"),
Inserting
alias App1.Repo
%User{}
|> User.changeset(%{name: "Alice", email: "alice@example.com", age: 30})
|> Repo.insert() # Returns {:ok, user} or {:error, changeset}
Updating
{:ok, user} = Repo.get(User, 1)
user
|> User.changeset(%{age: 7})
|> Repo.update()
User
|> Query.where(id: 1)
|> Repo.update_all(inc: [age: 1])
Query
Repo.all(App1.User)
Repo.get_by(App1.User, email: "alice@example.com")
Use the from macro:
import Ecto.Query
from u in User,
where: u.age > 18,
order_by: u.name,
select: [u.id, u.email]
|> Repo.all
Since from is a macro, you need to unquote variables using ^:
from u in User,
where: u.age > ^min_age
Tackle the N+1 Problem
users = Repo.preload(Repo.all(User, :posts))