cheatsheets/phoenix-ecto.md

210 lines
3.6 KiB
Markdown

---
title: "Phoenix: Ecto"
category: Elixir
tags: [WIP]
updated: 2017-08-30
---
## Schemas
{: .-three-column}
### Generating
```bash
$ mix phx.gen.html \
Accounts \ # domain
Profile \ # schema
profiles \ # table name
email:string \
age:integer
```
### Schema
```elixir
defmodule Myapp.Accounts.User do
use Ecto.Schema
schema "users" do
field :name
field :age, :integer
field :password, virtual: true
timestamps()
end
end
```
### Field types
| Field |
| --- |
| `:id` |
| `:binary` |
| `:boolean` |
| `:string` |
| --- |
| `:integer` |
| `:float` |
| `:decimal` |
| --- |
| `{:array, inner_type}` |
| `:map` |
{: .-left-align}
## Changesets
### Changesets
```elixir
def changeset(user, params \\ :empty) do
%User{}
|> Ecto.Changeset.change # basic casting to changeset
user
|> cast(params, ~w(name email), ~w(age)) # params to Changeset
|> validate_format(:email, ~r/@/)
|> validate_inclusion(:age, 18..100)
|> validate_exclusion(:role, ~w(admin superadmin))
|> validate_subset(:pets, ~w(cat dog parrot whale))
|> validate_length(:body, min: 1)
|> validate_length(:body, min: 1, max: 160)
|> validate_length(:partners, is: 2)
|> validate_number(:pi, greater_than: 3)
|> validate_number(:pi, less_than: 4)
|> validate_number(:pi, equal_to: 42)
|> validate_change(:title, fn _, _ -> [])
|> validate_confirmation(:password, message: "does not match")
|> unique_constraint(:email)
|> foreign_key_constraint(:post_id)
|> assoc_constraint(:post) # ensure post_id exists
|> no_assoc_constraint(:post) # negative (useful for deletions)
end
```
### Changeset fields
```elixir
changeset.valid?
changeset.errors #=> [title: "empty"]
changeset.changes #=> %{}
changeset.params[:title]
changeset.required #=> [:title]
changeset.optional #=> [:body]
```
### Updating
```elixir
changeset #(or model)
|> change(title: "New title")
|> change(%{ title: "New title" })
|> put_change(:title, "New title")
|> force_change(:title, "New title")
|> update_change(:title, &(&1 <> "..."))
|> delete_change(:title)
|> merge(other_changeset)
|> add_error(:title, "empty")
```
### Getting
```elixir
get_change(changeset, :title) #=> "hi" (if changed)
get_field(changeset, :title) #=> "hi" (even if unchanged)
fetch_change(changeset, :title) #=> {:ok, "hi"} | :error
fetch_field(changeset, :title) #=> {:changes | :model, "value"} | :error
```
## Repo
### Get one
```elixir
Repo.get(User, id)
Repo.get_by(User, email: "john@hello.com") #=> %User{} | nil
# also get! get_by!
```
### Create/update
```elixir
changeset |> Repo.update
changeset |> Repo.insert
changeset |> Repo.insert_or_update
```
```
User
|> Ecto.Changeset.change(%{name: "hi"})
|> Repo.insert
```
## Many
### Queries
```elixir
from p in Post,
where: p.title == "Hello",
where: [state: "Sweden"],
limit: 1,
offset: 10,
order_by: c.name,
order_by: [c.name, c.title],
order_by: [asc: c.name, desc: c.title],
preload: [:comments],
preload: [comments: {c, likes: l}],
join: c in assoc(c, :comments),
join: p in Post, on: c.post_id == p.id,
group_by: p,
select: p,
select: {p.title, p.description},
select: [p.title, p.description],
```
### Get many
```elixir
Repo.all(User)
```
### Update many
```elixir
Repo.update_all(Post, set: [title: "Title"])
Repo.update_all(Post, inc: [views: 1])
```
### Chaining `_all` with queries
```elixir
from(p in Post, where: p.id < 10)
|> Repo.update_all(...)
from(p in Post, where: p.id < 10)
|> Repo.all()
```
## References
{: .-one-column}
- Based on Ecto 1.3.