Schema
Schema (Structs)
Default id of type integer and timestamps() creates inserted/updated at field.
defmodule User do
use Ecto.Schema
@derive {Jason.Encoder, only: [:name, :age, :company_id]} #for sending %User through HTTP requests
schema "users" do #users is table name
field :name, :string
field :age, :integer, default: 0
field :links, {:array, :string}
has_many :posts, Post
belongs_to :company, MyApp.Company
end
end
defmodule Comment do
use Ecto.Schema
schema "comments" do
field :content, :string
field :parent_id, :integer
belongs_to :parent, Comment, foreign_key: :id, references: :parent_id, define_field: false
has_many :children, Comment, foreign_key: :parent_id, references: :id
end
end
user = %User{name: "jane"}
Finally, schemas can also have virtual fields by passing the virtual: true
option.
Example
Simple User
defmodule Discuss.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :username, :string
has_one :credential, Discuss.Accounts.Credential
timestamps()
end
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :username]) #requires both
|> validate_required([:name, :username])
|> unique_constraint(:username)
end
end
Password Hash Example
defmodule Discuss.Accounts.Credential do
use Ecto.Schema
import Ecto.Changeset
alias Comeonin.Argon2
schema "credentials" do
field :email, :string
field :password_hash, :string
field :password, :string, virtual: true #Dont get saved
field :password_confirmation, :string, virtual: true
belongs_to :user, Discuss.Accounts.User
timestamps()
end
@doc false
def changeset(credential, attrs) do
credential
|> cast(attrs, [:email])
|> validate_required([:email])
|> validate_format(:email, ~r/@/ )
|> unique_constraint(:email)
end
#if validation fails, will still pass to next changeset with valid: false
def registration_changeset(struct, attrs \\ %{}) do
struct
|> changeset(attrs)
|> cast(attrs, [:password, :password_confirmation]) #cast is just the attrs you pay attention(throws away rest)
|> validate_required([:password, :password_confirmation])
|> validate_length(:password, min: 8)
|> validate_confirmation(:password) #compares password and password_confirmation are equal, other arg assumed by f
|> hash_password()
end
def hash_password(%{valid?: false} = changeset), do: changeset
def hash_password(%{valid?: true, changes: %{password: pass}} = changeset) do
put_change(changeset, :password_hash, Argon2.hashpwsalt(pass))
end
end
User Context with this Schema
defmodule Discuss.Accounts do
@moduledoc """
The Accounts context.
"""
import Ecto.Query, warn: false
import Comeonin.Argon2, only: [checkpw: 2, dummy_checkpw: 0]
alias Discuss.Repo
alias Discuss.Accounts.{Credential, User}
def list_users do
Repo.all(User)
end
def get_user!(id) do
Repo.get!(User, id)
|> Repo.preload(:credential)
end
def create_user(attrs \\ %{}) do
%User{}
|> User.changeset(attrs)
|> Ecto.Changeset.cast_assoc(:credential, with: &Credential.registration_changeset/2)
|> Repo.insert()
end
def update_user(%User{} = user, attrs) do
# Only way to update user now, for some reasons won't highlight in Typora correclty so commented out
#cred_changeset =
#if attrs["credential"] ["password"] == "" do
&Credential.changeset/2
else
&Credential.registration_changeset/2
end
user
|> User.changeset(attrs)
|> Ecto.Changeset.cast_assoc(:credential, with: cred_changeset)
|> Repo.update()
end
def delete_user(%User{} = user) do
Repo.delete(user)
end
def change_user(%User{} = user) do
User.changeset(user, %{})
end
alias Discuss.Accounts.Credential
def list_credentials do
Repo.all(Credential)
end
def get_credential!(id), do: Repo.get!(Credential, id)
def create_credential(attrs \\ %{}) do
%Credential{}
|> Credential.changeset(attrs)
|> Repo.insert()
end
def update_credential(%Credential{} = credential, attrs) do
credential
|> Credential.changeset(attrs)
|> Repo.update()
end
def delete_credential(%Credential{} = credential) do
Repo.delete(credential)
end
def change_credential(%Credential{} = credential) do
Credential.changeset(credential, %{})
end
end
Extra
lib/hello/repo.ex
defmodule Hello.Repo do
use Ecto.Repo, #import query functions
otp_app: :hello, #set app name
adapter: Ecto.Adapters.Postgres #setup adapter
end
Changeset
changeset = User.changeset(%User{}, %{})
#Ecto.Changeset<action: nil, changes: %{}, errors: [name: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}, bio: {"can't be blank", [validation: :required]},
data: #Hello.User<>, valid?: false>
changeset.valid? # false
Last updated