2. Ecto
● Framework de persistencia para Elixir (hecho en Elixir)
● Diseñado para bases de datos relacionales
● Ideas de LINQ de .NET y ActiveRecord de Rails
● Wrapper de la base de datos
● Lenguaje de consulta integrado (language integrated
query - LIQ) !!
○ DSL
3. LINQ & Ecto & SQL
# Elixir
from c in Customer,
where: c.city == “Mexico”,
select: c.company_name
// C#
from c in db.Customers
where c.City == “Mexico”
select c.CompanyName
-- SQL
SELECTcompany_name
FROM customer
WHERE city = ‘Mexico’
4. Beneficios de un LIQ
● Metadatos
● Validación de sintaxis en tiempo de compilación
● Tipos
● Seguridad
● Composición
7. Ecto.Repo
● Wrapper de la base de datos
● Via el Repo, creamos, consultamos, borramos y
actualizamos los datos
● Usa un Adapter para comunicarse a la BD
8. Crear el proyecto con mix
● > mix new wsp_app --sup
● --sup para generar una aplicación OTP con árbol de
supervisión
● Ecto.Repo corre bajo un árbol de supervisión
9. Dependencias
# mix.exs
defp deps do
[{:postgrex, ">= 0.0.0"},
{:ecto, "~> 2.0.0"}]
end
# mix.exs
def application do
[applications: [:logger, :ecto, :postgrex],
mod: {WspApp, []}]
end
10. Repositorio y árbol de supervisión
# Nuevo archivo /lib/wsp_app/repo.ex
defmodule WspApp.Repo do
use Ecto.Repo, otp_app: :wsp_app
End
# Agregar el repo al árbol de supervisión /lib/wsp_app.ex
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [supervisor(WspApp.Repo, [])]
opts = [strategy: :one_for_one, name: WspApp.Supervisor]
Supervisor.start_link(children, opts)
end
12. Ecto.Schema
● Mapea la fuente de datos a un struct de Elixir
● Usado para mapear la tabla a datos de Elixir
13. Migration - workshop_proposals
# > mix ecto.gen.migration create_workshop_proposals_table
# /priv/repo/migrations/
defmodule WspApp.Repo.Migrations.CreateWorkshopProposalsTable do
use Ecto.Migration
def change do
create table(:workshop_proposals) do
add :title, :text
add :description, :text
add :instructor_email, :text
add :tentative_date, :date
timestamps
end
end
end
14. Migration - votes
# > mix ecto.gen.migration create_votes_table
# /priv/repo/migrations/
defmodule WspApp.Repo.Migrations.CreateVotesTable do
use Ecto.Migration
def change do
create table(:votes) do
add :email, :text
add :workshop_proposal_id,
references(:workshop_proposals, [on_delete: :delete_all, on_update: :update_all])
timestamps
end
end
end
15. Migration - unique index para votes
# > mix ecto.gen.migration create_unique_vote_constraint
# /priv/repo/migrations/
defmodule WspApp.Repo.Migrations.CreateUniqueVoteConstraint do
use Ecto.Migration
def change do
create unique_index(:votes, [:email, :workshop_proposal_id])
end
end
16. Modelo - WorkshopProposal
# /lib/wsp_app/workshop_proposal.ex
defmodule WspApp.WorkshopProposal do
use Ecto.Schema
schema "workshop_proposals" do
field :title, :string
field :description, :string
field :instructor_email, :string
field :tentative_date, Ecto.Date
has_many :votes, WspApp.Vote
timestamps
end
end
17. Modelo - Vote
# /lib/wsp_app/vote.ex
defmodule WspApp.Vote do
use Ecto.Schema
import Ecto.Changeset
schema "votes" do
field :email, :string
belongs_to :workshop_proposal, WspApp.WorkshopProposal, foreign_key: :workshop_proposal_id
timestamps
end
end
18. Ecto.Changeset
● Filtran y hacen ‘cast’ de parametros externos.
● Provee mecanismos para dar seguimiento (tracking) y
validación de cambios.
19. WorkshopProposal Changeset v1
# /lib/wsp_app/workshop_proposal.ex
defmodule WspApp.WorkshopProposal do
use Ecto.Schema
import Ecto.Changeset
# ... schema
def changeset(workshop_proposal, params %{}) do
workshop_proposal
|> cast(params, [:title, :description, :instructor_email, :tentative_date])
|> validate_required([:title, :instructor_email, :tentative_date])
end
end
20. WorkshopProposal Changeset v2
def changeset(workshop_proposal, params %{}) do
workshop_proposal
|> cast(params, [:title, :description, :instructor_email, :tentative_date])
|> validate_required([:title, :instructor_email, :tentative_date])
|> validate_format(:instructor_email, ~r/@/)
|> validate_change(:tentative_date, fn(:tentative_date, tentative_date) ->
case Ecto.Date.compare(tentative_date, Ecto.Date.from_erl(:erlang.date())) do
:lt -> [tentative_date: "cannot be less than today"]
:gt -> []
:eq -> []
end
end)
end
21. Vote Changeset
# /lib/wsp_app/vote.ex
defmodule WspApp.Vote do
use Ecto.Schema
import Ecto.Changeset
# ...
def changeset(vote, params %{}) do
vote
|> cast(params, [:email, :workshop_proposal_id])
|> validate_required([:email, :workshop_proposal_id])
|> validate_format(:email, ~r/@/)
end
end
22. Ecto.Changeset Struct
● Algunos campos son:
○ valid? - si el changeset es válido
○ data - la fuente de datos
○ changes - los cambios aplicados
○ errors - los errores después de las validaciones
23. Ecto.Query
● Consultas SQL escritas en Elixir
● Son seguras porque evitan problemas como SQL Injection
● Son “armables”
24. Consultas sencillas
import Ecto.Query
# SELECT * FROM workshop_proposal
WspApp.Repo.all(from w in WspApp.WorkshopProposal)
# SELECT id, title, instructor_email FROM workshop_proposal
WspApp.Repo.all(from w in WspApp.WorkshopProposal,
select: [w.id, w.title, w.instructor_email])
# SELECT * from workshop_proposal WHERE instructor_email = 'rgutierrez@nearsoft.com'
WspApp.Repo.all(from w in WspApp.WorkshopProposal,
where: w.instructor_email == "rgutierrez@nearsof.com")
25. Interpolacion y ‘Casting’
import Ecto.Query
date = Ecto.Date.cast!("2016-09-10")
WspApp.Repo.all(from w in WspApp.WorkshopProposal, where: w.tentative_date > ^date)
email = "rgutierrez@nearsoft.com"
WspApp.Repo.all(from w in WspApp.WorkshopProposal, where: w.instructor_email == ^email)
# Error: ** (Ecto.QueryError) iex:7: value `"0"` cannot be dumped to type :id.
WspApp.Repo.all(from w in WspApp.WorkshopProposal, where: w.id > "0")
26. Composicion
query = from w in WspApp.WorkshopProposal,
where: w.tentative_date > ^Ecto.Date.cast!("2016-09-10")
WspApp.Repo.all(query)
WspApp.Repo.all(from w in query, select: w.title)
WspApp.Repo.all(from w in query,
select: {w.title, w.instructor_email},
where: w.instructor_email not in ["rgutierrez@nearsoft.com"])
27. Paginación
WspApp.Repo.all(from w in WspApp.WorkshopProposal,
limit: 10,
offset: 0)
WspApp.Repo.all(from w in WspApp.WorkshopProposal,
limit: 10,
offset: 10)
28. Operadores de Agregación y Joins
WspApp.Repo.all(from w in WspApp.WorkshopProposal,
join: v in WspApp.Vote, on: v.workshop_proposal_id == w.id,
where: v.workshop_proposal_id == w.id,
select: {w.title, count(v.id)},
group_by: w.title)
# gracias a la asociación
WspApp.Repo.all(from w in WspApp.WorkshopProposal,
join: v in WspApp.Vote,
where: v.workshop_proposal_id == w.id,
select: {w.title, count(v.id)},
group_by: w.title)
● count, avg, sum, min, max, group_by, having, order_by
29. Fragments
● Cuando se requiere usar funciones/operaciones
específicas del manejador de base de datos.
from p in Post,
where: is_nil(p.published_at) and
fragment("downcase(?)", p.title) == ^title
startTime = Ecto.DateTime.cast!({{2016, 11, 11}, {18, 00, 00}})
endTime = Ecto.DateTime.cast!({{2016, 11, 11}, {20, 00, 00}})
from c in Workshop,
where: fragment("(?, ?) OVERLAPS (?, ?)",
c.start, c.end, type(^startTime, Ecto.DateTime), type(^endTime, Ecto.DateTime))