Ecto DSL
Domain specific language for writing queries and interacting with
databases in Elixir.
Yurii Bodarev
Back-end software developer
twitter.com/bodarev_yurii
github.com/yuriibodarev
Ecto official resources
github.com/elixir-ecto/ecto
hexdocs.pm/ecto/Ecto.html
pages.plataformatec.com.br/ebook-whats-new-in-ecto-2-0
Brief contents
• Elixir Data Structures
• Basic data types and structures
• Associative data structures
• Structs
• Pattern Matching
• Ecto
• Ecto.Repo
• Ecto.Schema
• Ecto.Changeset
• Ecto.Query
Basic data types and structures
• Atoms
• Lists
• Tuples
Atoms
Atoms are constants where their name is their own value.
iex> :hello
:hello
iex> :hello == :world
false
iex> true == :true
true
Lists (Linked)
Lists are used to manage dynamic, variable-sized collections of data of
any type.
iex> [1, "abc", true, 3]
[1, "abc", true, 3]
iex> length([1, 2, 3])
3
List - recursive structure
[head | tail]
iex> [1 | [2, 3, 4]]
[1, 2, 3, 4]
[head | [head | [head | tail…]]]
iex> [1 | [2 | [3 | [4 | []]]]]
[1, 2, 3, 4]
List - recursive structure
iex> hd([1, 2, 3, 4])
1
iex> tl([1, 2, 3, 4])
[2, 3, 4]
iex> tl([2, 3, 4])
[3, 4]
iex> tl([4])
[]
Tuples
Tuples are untyped structures often used to group a fixed number of elements
together.
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2
Associative data structures
• Keyword lists
• Maps
List as key-value data structure
It is common to use a list of 2-item tuples as the representation of a
key-value data structure
iex> list = [{"a", 1}, {"b", 2}, {"c", 3}]
[{"a", 1}, {"b", 2}, {"c", 3}]
iex> List.keyfind(list, "b", 0)
{"b", 2}
Keyword lists
When we have a list of tuples and the first item of the tuple (i.e. the
key) is an atom, we call it a keyword list.
iex> [{:a, 1}, {:b, 2}, {:c, 3}]
[a: 1, b: 2, c: 3]
Keyword lists
Elixir supports a special syntax for defining such lists: [key: value]
iex> [a: 1, b: 2, c: 3] == [{:a, 1}, {:b, 2}, {:c, 3}]
true
• Keys must be atoms.
• Keys are ordered, as specified by the developer.
• Keys can be given more than once.
We can use all operations available to lists on keyword lists
iex> list = [a: 1, c: 3, b: 2]
[a: 1, c: 3, b: 2]
iex> hd(list)
{:a, 1}
iex> tl(list)
[c: 3, b: 2]
iex> list[:a]
1
iex> newlist = [a: 0] ++ list
[a: 0, a: 1, c: 3, b: 2]
iex> newlist[:a]
0
iex> list[:d]
nil
Keyword lists - default mechanism for passing options to
functions in Elixir
iex> if true, do: "THIS"
"THIS"
iex> if false, do: "THIS", else: "THAT"
"THAT"
iex> if(false, [do: "THIS", else: "THAT"])
"THAT"
When the keyword list is the last argument of a function, the square brackets
are optional.
Example of the Ecto query
query = from w in Weather,
where: w.prcp > 0,
where: w.temp < 20,
select: w
Maps
A map is a key-value store, where keys and values can be any term. A map is created using
the %{} syntax. Maps’ keys do not follow developer ordering.
iex> map = %{:a => 1, 2 => "b", "c" => 3}
%{2 => "b", :a => 1, "c" => 3}
iex> map[:a]
1
iex> map[2]
"b"
iex> map["d"]
nil
Maps
When all the keys in a map are atoms, you can use the keyword syntax.
iex> map = %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}
iex> map.a
1
iex> map.d
** (KeyError) key :d not found in: %{a: 1, b: 2, c: 3}
iex> %{map | c: 5}
%{a: 1, b: 2, c: 5}
iex> %{map | d: 0}
** (KeyError) key :d not found in: %{a: 1, b: 2, c: 3}
Structs
Structs are extensions built on top of maps that provide compile-time
checks and default values.
iex> defmodule User do
...> defstruct name: "Ivan", age: 25
...> end
iex> %User{}
%User{age: 25, name: "Ivan"}
Structs
iex> %User{name: "Maria"}
%User{age: 25, name: "Maria"}
iex> %User{other: "Something"}
** (KeyError) key :other not found in: %User{age:
25, name: "Ivan"}
Structs are bare maps underneath, but none of the protocols
implemented for maps are available for structs
iex> ivan = %User{}
%User{age: 25, name: "Ivan"}
iex> is_map(ivan)
true
iex> Map.keys(ivan)
[:__struct__, :age, :name]
iex> ivan.__struct__
User
iex> ivan[:age]
** (UndefinedFunctionError)
function User.fetch/2 is
undefined (User does not
implement the Access behaviour)
Pattern matching
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
iex> c
42
A pattern match will error if the sides can’t be
matched
iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value:
{:hello, "world"}
iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value:
[:hello, "world", 42]
We can match on specific values
iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13
iex> {:ok, result} = {:error, "Not Found!"}
** (MatchError) no match of right hand side value:
{:error, "Not Found!"}
We can match on specific values
post = Repo.get!(Post, 42)
case Repo.delete post do
{:ok, struct} -> # Deleted with success
{:error, changeset} -> # Something went wrong
end
Pattern match on lists
iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> b
2
iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]
iex> [] = [1, 2, 3]
** (MatchError) no match of
right hand side value: [1, 2, 3]
Pattern match on keyword lists
iex> [a: a] = [a: 1]
[a: 1]
iex> [a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
iex> [b: b, a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
Pattern match on maps
iex> %{} = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> %{b: b} = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> b
2
iex> %{c: c} = %{a: 1, b: 2}
** (MatchError) no match of right hand side value: %{a: 1, b: 2}
The pin ^ operator and _
iex> x = 2
2
iex> {1, ^x} = {1, 2}
{1, 2}
iex> {a, _} = {1, 2}
{1, 2}
iex> a
1
Ecto
Ecto is split into 4 main components:
• Ecto.Repo - repositories are wrappers around the data store.
• Ecto.Schema - schemas are used to map any data source into an Elixir
struct.
• Ecto.Changeset - allow developers to filter, cast, and validate changes
before we apply them to the data.
• Ecto.Query - written in Elixir syntax, queries are used to retrieve
information from a given repository.
Ecto playground
Ecto in not an ORM
github.com/yuriibodarev/Ecto_not_ORM
Requires: PostgreSQL
Run within IEx console: iex -S mix
Repositories
Via the repository, we can create, update, destroy and query existing
database entries.
Repositories
Ecto.Repo is a wrapper around the database. We can define a
repository as follows (libblogrepo.ex):
defmodule Blog.Repo do
use Ecto.Repo, otp_app: :blog
end
Repositories
A repository needs an adapter and credentials to communicate to the database.
Configuration for the Repo usually defined in your config/config.exs:
config :blog, Blog.Repo,
adapter: Ecto.Adapters.Postgres,
database: "blog_repo",
username: "postgres",
password: "postgres",
hostname: "localhost"
Repositories
Each repository in Ecto defines a start_link/0. Usually this function is invoked as part
of your application supervision tree (libblog.ex):
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [ worker(Blog.Repo, []), ]
opts = [strategy: :one_for_one, name: Blog.Supervisor]
Supervisor.start_link(children, opts)
end
Schema
Schemas allows developers to define the shape of their data. (libbloguser.ex)
defmodule Blog.User do
use Ecto.Schema
schema "users" do
field :name, :string
field :reputation, :integer, default: 0
has_many :posts, Blog.Post, on_delete: :delete_all
timestamps
end
end
Schema
By defining a schema, Ecto automatically defines a struct:
iex> user = %Blog.User{name: "Bill"}
%Blog.User{__meta__: #Ecto.Schema.Metadata<:built,
"users">, id: nil, inserted_at: nil, name: "Bill"},
posts: #Ecto.Association.NotLoaded<association
:posts is not loaded>, reputation: 0, updated_at:
nil}
Schema
Using Schema we can interact with a repository:
iex> user = %Blog.User{name: "Bill", reputation: 10}
%Blog.User{…}
iex> Blog.Repo.insert!(user)
%Blog.User{__meta__: #Ecto.Schema.Metadata<:loaded,
"users">, id: 6, inserted_at: ~N[2016-12-13
16:16:35.983000], name: "Bill", posts:
#Ecto.Association.NotLoaded<association :posts is not
loaded>, reputation: 10, updated_at: ~N[2016-12-13
16:16:36.001000]}
Schema
# Get the user back
iex> newuser = Blog.Repo.get(Blog.User, 6)
iex> newuser.id
6
# Delete it
iex> Blog.Repo.delete(newuser)
{:ok, %Blog.User{…, id: 6,…}}
Schema
We can use pattern matching on Structs created with Schemas:
iex> %{name: name, reputation: reputation} =
...> Blog.Repo.get(Blog.User, 1)
iex> name
"Alex"
iex> reputation
144
Changesets
We can add changesets to our schemas to validate changes before we
apply them to the data (libbloguser.ex):
def changeset(user, params  %{}) do
user
|> cast(params, [:name, :reputation])
|> validate_required([:name, :reputation])
|> validate_inclusion(:reputation, -999..999)
end
Changesets
iex> alina = %Blog.User{name: "Alina"}
iex> correct_changeset = Blog.User.changeset(alina, %{reputation: 55})
#Ecto.Changeset<action: nil, changes: %{reputation: 55}, errors: [],
data: #Blog.User<>, valid?: true>
iex> invalid_changeset = Blog.User.changeset(alina, %{reputation: 1055})
#Ecto.Changeset<action: nil, changes: %{reputation: 1055}, errors:
[reputation: {"is invalid", [validation: :inclusion]}], data:
#Blog.User<>, valid?: false>
Changeset with Repository functions
iex> valid_changeset.valid?
true
iex> Blog.Repo.insert(valid_changeset)
{:ok, %Blog.User{…, id: 7, …}}
Changeset with Repository functions
iex> invalid_changeset.valid?
false
iex> Blog.Repo.insert(invalid_changeset)
{:error, #Ecto.Changeset<action: :insert, changes:
%{reputation: 1055}, errors: [reputation: {"is
invalid", [validation: :inclusion]}], data:
#Blog.User<>, valid?: false>}
Changeset with Repository functions
case Blog.Repo.update(changeset) do
{:ok, user} ->
# user updated
{:error, changeset} ->
# an error occurred
end
We can provide different changeset functions
for different use cases
def registration_changeset(user, params) do
# Changeset on create
end
def update_changeset(user, params) do
# Changeset on update
end
Query
Ecto allows you to write queries in Elixir and send them to the
repository, which translates them to the underlying database.
Query using predefined Schema
# Query using predefined Schema
query = from u in User,
where: u.reputation > 35,
select: u
# Returns %User{} structs matching the query
Repo.all(query)
[%Blog.User{…, id: 2, …, name: "Bender", …, reputation: 42, …},
%Blog.User{…, id: 1, …, name: "Alex", …, reputation: 144, …}]
Directly querying the “users” table
# Directly querying the “users” table
query = from u in "users",
where: u.reputation > 30,
select: %{name: u.name, reputation: u.reputation}
# Returns maps as defined in select
Repo.all(query)
[%{name: "Bender", reputation: 42}, %{name: "Alex", reputation: 144}]
External values in Queries
# ^ operator
min = 33
query = from u in "users",
where: u.reputation > ^min,
select: u.name
# casting
mins = "33"
query = from u in "users",
where: u.reputation > type(^mins, :integer),
select: u.name
External values in Queries
If query is created with predefined Schema than Ecto
will automatically cast external value
min = "35"
Repo.all(from u in User, where: u.reputation > ^min)
You can also skip Select to retrieve all fields specified in the Schema
Associations
schema "users" do
field :name, :string
field :reputation, :integer, default: 0
has_many :posts, Blog.Post, on_delete: :delete_all
timestamps
end
Associations
alex = Repo.get_by(User, name: "Alex")
%Blog.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 1,
inserted_at: ~N[2016-12-17 06:36:54.916000], name: "Alex",
posts: #Ecto.Association.NotLoaded<association :posts is not loaded>,
reputation: 13, updated_at: ~N[2016-12-17 06:36:54.923000]}
Associations
alex_wposts = Repo.preload(alex, :posts)
%Blog.User{…, id: 1, name: "Alex",
posts: [%Blog.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
body: "World",
comments: #Ecto.Association.NotLoaded<association :comments is not loaded>,
id: 1, inserted_at: ~N[2016-12-17 06:36:54.968000], pinned: true,
title: "Hello", updated_at: ~N[2016-12-17 06:36:54.968000],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 1}], …}
Associations
users_wposts = from u in User,
where: u.reputation > 10,
order_by: [asc: u.name],
preload: [:posts]
Queries Composition
alex = Repo.get_by(User, name: "Alex")
alex_post = from p in Post,
where: p.user_id == ^alex.id
alex_pin = from ap in alex_post,
where: ap.pinned == true
Fragments
title = "hello"
from(p in Post,
where: fragment("lower(?)", p.title) == ^title)
|> Repo.all()
THANK YOU!!!

More Related Content

PPTX
Java Foundations: Maps, Lambda and Stream API
PPTX
Java Foundations: Lists, ArrayList<T>
PPTX
Introduction to python programming 1
PDF
Arrays in python
PPTX
PDF
Basic data structures in python
PDF
Elixir in a nutshell - Fundamental Concepts
PPTX
Introduction to python programming 2
Java Foundations: Maps, Lambda and Stream API
Java Foundations: Lists, ArrayList<T>
Introduction to python programming 1
Arrays in python
Basic data structures in python
Elixir in a nutshell - Fundamental Concepts
Introduction to python programming 2

What's hot (19)

PDF
1. python
PDF
Where's My SQL? Designing Databases with ActiveRecord Migrations
PPTX
Python Modules and Libraries
PDF
Magicke metody v Pythonu
PDF
Introduction to python programming
PDF
Python programming : Arrays
PDF
Functional es6
PPTX
Ggplot2 v3
PDF
Swift에서 꼬리재귀 사용기 (Tail Recursion)
PPT
Python tutorialfeb152012
PDF
Python Workshop Part 2. LUG Maniapl
PPTX
Farhana shaikh webinar_dictionaries
PDF
Python dictionary : past, present, future
PPT
Functional Programming
PPT
Python tutorial
PDF
Begin with Python
PDF
Closure, Higher-order function in Swift
PDF
Monads and Monoids by Oleksiy Dyagilev
PPTX
Python programming Part -6
1. python
Where's My SQL? Designing Databases with ActiveRecord Migrations
Python Modules and Libraries
Magicke metody v Pythonu
Introduction to python programming
Python programming : Arrays
Functional es6
Ggplot2 v3
Swift에서 꼬리재귀 사용기 (Tail Recursion)
Python tutorialfeb152012
Python Workshop Part 2. LUG Maniapl
Farhana shaikh webinar_dictionaries
Python dictionary : past, present, future
Functional Programming
Python tutorial
Begin with Python
Closure, Higher-order function in Swift
Monads and Monoids by Oleksiy Dyagilev
Python programming Part -6
Ad

Similar to Ecto DSL Introduction - Yurii Bodarev (20)

PDF
Elixir cheatsheet
PDF
Introduction to Elixir
PDF
Elixir talk
PDF
Pre-Bootcamp introduction to Elixir
KEY
Clojure Intro
PDF
Ruby to Elixir - what's great and what you might miss
PDF
Elixir + Neo4j
PDF
Elixir: the not-so-hidden path to Erlang
ODP
Elixir basics
PDF
learn you some erlang - chap 9 to chap10
PDF
Introducing Elixir and OTP at the Erlang BASH
PDF
Pune Clojure Course Outline
PDF
(first '(Clojure.))
PDF
Malli: inside data-driven schemas
ODP
Very basic functional design patterns
PPTX
Python Data Structures and Algorithms.pptx
PDF
The Curious Clojurist - Neal Ford (Thoughtworks)
PDF
Introduction to clojure
PPTX
Elixir cheatsheet
Introduction to Elixir
Elixir talk
Pre-Bootcamp introduction to Elixir
Clojure Intro
Ruby to Elixir - what's great and what you might miss
Elixir + Neo4j
Elixir: the not-so-hidden path to Erlang
Elixir basics
learn you some erlang - chap 9 to chap10
Introducing Elixir and OTP at the Erlang BASH
Pune Clojure Course Outline
(first '(Clojure.))
Malli: inside data-driven schemas
Very basic functional design patterns
Python Data Structures and Algorithms.pptx
The Curious Clojurist - Neal Ford (Thoughtworks)
Introduction to clojure
Ad

More from Elixir Club (20)

PDF
Kubernetes + Docker + Elixir - Alexei Sholik, Andrew Dryga | Elixir Club Ukraine
PDF
Integrating 3rd parties with Ecto - Eduardo Aguilera | Elixir Club Ukraine
PDF
— An async template - Oleksandr Khokhlov | Elixir Club Ukraine
PDF
BEAM architecture handbook - Andrea Leopardi | Elixir Club Ukraine
PDF
You ain't gonna need write a GenServer - Ulisses Almeida | Elixir Club Ukraine
PDF
— Knock, knock — An async templates — Who’s there? - Alexander Khokhlov | ...
PDF
Performance measurement methodology — Maksym Pugach | Elixir Evening Club 3
PDF
Erlang cluster. How is it? Production experience. — Valerii Vasylkov | Elixi...
PDF
Promo Phx4RailsDevs - Volodya Sveredyuk
PDF
Web of today — Alexander Khokhlov
PDF
ElixirConf Eu 2018, what was it like? – Eugene Pirogov
PDF
Implementing GraphQL API in Elixir – Victor Deryagin
PDF
WebPerformance: Why and How? – Stefan Wintermeyer
PDF
GenServer in Action – Yurii Bodarev
PDF
Russian Doll Paradox: Elixir Web without Phoenix - Alex Rozumii
PDF
Practical Fault Tolerance in Elixir - Alexei Sholik
PDF
Phoenix and beyond: Things we do with Elixir - Alexander Khokhlov
PDF
Monads are just monoids in the category of endofunctors - Ike Kurghinyan
PDF
Craft effective API with GraphQL and Absinthe - Ihor Katkov
PDF
Elixir in a service of government - Alex Troush
Kubernetes + Docker + Elixir - Alexei Sholik, Andrew Dryga | Elixir Club Ukraine
Integrating 3rd parties with Ecto - Eduardo Aguilera | Elixir Club Ukraine
— An async template - Oleksandr Khokhlov | Elixir Club Ukraine
BEAM architecture handbook - Andrea Leopardi | Elixir Club Ukraine
You ain't gonna need write a GenServer - Ulisses Almeida | Elixir Club Ukraine
— Knock, knock — An async templates — Who’s there? - Alexander Khokhlov | ...
Performance measurement methodology — Maksym Pugach | Elixir Evening Club 3
Erlang cluster. How is it? Production experience. — Valerii Vasylkov | Elixi...
Promo Phx4RailsDevs - Volodya Sveredyuk
Web of today — Alexander Khokhlov
ElixirConf Eu 2018, what was it like? – Eugene Pirogov
Implementing GraphQL API in Elixir – Victor Deryagin
WebPerformance: Why and How? – Stefan Wintermeyer
GenServer in Action – Yurii Bodarev
Russian Doll Paradox: Elixir Web without Phoenix - Alex Rozumii
Practical Fault Tolerance in Elixir - Alexei Sholik
Phoenix and beyond: Things we do with Elixir - Alexander Khokhlov
Monads are just monoids in the category of endofunctors - Ike Kurghinyan
Craft effective API with GraphQL and Absinthe - Ihor Katkov
Elixir in a service of government - Alex Troush

Recently uploaded (20)

PDF
BoxLang Dynamic AWS Lambda - Japan Edition
PPTX
Full-Stack Developer Courses That Actually Land You Jobs
PDF
Ableton Live Suite for MacOS Crack Full Download (Latest 2025)
PPTX
MLforCyber_MLDataSetsandFeatures_Presentation.pptx
PPTX
Introduction to Windows Operating System
PDF
DuckDuckGo Private Browser Premium APK for Android Crack Latest 2025
PDF
AI/ML Infra Meetup | LLM Agents and Implementation Challenges
PDF
MCP Security Tutorial - Beginner to Advanced
PDF
DNT Brochure 2025 – ISV Solutions @ D365
PPTX
Tech Workshop Escape Room Tech Workshop
PDF
Autodesk AutoCAD Crack Free Download 2025
PDF
Multiverse AI Review 2025: Access All TOP AI Model-Versions!
PDF
novaPDF Pro 11.9.482 Crack + License Key [Latest 2025]
PPTX
WiFi Honeypot Detecscfddssdffsedfseztor.pptx
PDF
AI/ML Infra Meetup | Beyond S3's Basics: Architecting for AI-Native Data Access
PDF
AI-Powered Threat Modeling: The Future of Cybersecurity by Arun Kumar Elengov...
DOCX
How to Use SharePoint as an ISO-Compliant Document Management System
PDF
EaseUS PDF Editor Pro 6.2.0.2 Crack with License Key 2025
PPTX
most interesting chapter in the world ppt
DOC
UTEP毕业证学历认证,宾夕法尼亚克拉里恩大学毕业证未毕业
BoxLang Dynamic AWS Lambda - Japan Edition
Full-Stack Developer Courses That Actually Land You Jobs
Ableton Live Suite for MacOS Crack Full Download (Latest 2025)
MLforCyber_MLDataSetsandFeatures_Presentation.pptx
Introduction to Windows Operating System
DuckDuckGo Private Browser Premium APK for Android Crack Latest 2025
AI/ML Infra Meetup | LLM Agents and Implementation Challenges
MCP Security Tutorial - Beginner to Advanced
DNT Brochure 2025 – ISV Solutions @ D365
Tech Workshop Escape Room Tech Workshop
Autodesk AutoCAD Crack Free Download 2025
Multiverse AI Review 2025: Access All TOP AI Model-Versions!
novaPDF Pro 11.9.482 Crack + License Key [Latest 2025]
WiFi Honeypot Detecscfddssdffsedfseztor.pptx
AI/ML Infra Meetup | Beyond S3's Basics: Architecting for AI-Native Data Access
AI-Powered Threat Modeling: The Future of Cybersecurity by Arun Kumar Elengov...
How to Use SharePoint as an ISO-Compliant Document Management System
EaseUS PDF Editor Pro 6.2.0.2 Crack with License Key 2025
most interesting chapter in the world ppt
UTEP毕业证学历认证,宾夕法尼亚克拉里恩大学毕业证未毕业

Ecto DSL Introduction - Yurii Bodarev

  • 1. Ecto DSL Domain specific language for writing queries and interacting with databases in Elixir.
  • 2. Yurii Bodarev Back-end software developer twitter.com/bodarev_yurii github.com/yuriibodarev
  • 4. Brief contents • Elixir Data Structures • Basic data types and structures • Associative data structures • Structs • Pattern Matching • Ecto • Ecto.Repo • Ecto.Schema • Ecto.Changeset • Ecto.Query
  • 5. Basic data types and structures • Atoms • Lists • Tuples
  • 6. Atoms Atoms are constants where their name is their own value. iex> :hello :hello iex> :hello == :world false iex> true == :true true
  • 7. Lists (Linked) Lists are used to manage dynamic, variable-sized collections of data of any type. iex> [1, "abc", true, 3] [1, "abc", true, 3] iex> length([1, 2, 3]) 3
  • 8. List - recursive structure [head | tail] iex> [1 | [2, 3, 4]] [1, 2, 3, 4] [head | [head | [head | tail…]]] iex> [1 | [2 | [3 | [4 | []]]]] [1, 2, 3, 4]
  • 9. List - recursive structure iex> hd([1, 2, 3, 4]) 1 iex> tl([1, 2, 3, 4]) [2, 3, 4] iex> tl([2, 3, 4]) [3, 4] iex> tl([4]) []
  • 10. Tuples Tuples are untyped structures often used to group a fixed number of elements together. iex> tuple = {:ok, "hello"} {:ok, "hello"} iex> elem(tuple, 1) "hello" iex> tuple_size(tuple) 2
  • 11. Associative data structures • Keyword lists • Maps
  • 12. List as key-value data structure It is common to use a list of 2-item tuples as the representation of a key-value data structure iex> list = [{"a", 1}, {"b", 2}, {"c", 3}] [{"a", 1}, {"b", 2}, {"c", 3}] iex> List.keyfind(list, "b", 0) {"b", 2}
  • 13. Keyword lists When we have a list of tuples and the first item of the tuple (i.e. the key) is an atom, we call it a keyword list. iex> [{:a, 1}, {:b, 2}, {:c, 3}] [a: 1, b: 2, c: 3]
  • 14. Keyword lists Elixir supports a special syntax for defining such lists: [key: value] iex> [a: 1, b: 2, c: 3] == [{:a, 1}, {:b, 2}, {:c, 3}] true • Keys must be atoms. • Keys are ordered, as specified by the developer. • Keys can be given more than once.
  • 15. We can use all operations available to lists on keyword lists iex> list = [a: 1, c: 3, b: 2] [a: 1, c: 3, b: 2] iex> hd(list) {:a, 1} iex> tl(list) [c: 3, b: 2] iex> list[:a] 1 iex> newlist = [a: 0] ++ list [a: 0, a: 1, c: 3, b: 2] iex> newlist[:a] 0 iex> list[:d] nil
  • 16. Keyword lists - default mechanism for passing options to functions in Elixir iex> if true, do: "THIS" "THIS" iex> if false, do: "THIS", else: "THAT" "THAT" iex> if(false, [do: "THIS", else: "THAT"]) "THAT" When the keyword list is the last argument of a function, the square brackets are optional.
  • 17. Example of the Ecto query query = from w in Weather, where: w.prcp > 0, where: w.temp < 20, select: w
  • 18. Maps A map is a key-value store, where keys and values can be any term. A map is created using the %{} syntax. Maps’ keys do not follow developer ordering. iex> map = %{:a => 1, 2 => "b", "c" => 3} %{2 => "b", :a => 1, "c" => 3} iex> map[:a] 1 iex> map[2] "b" iex> map["d"] nil
  • 19. Maps When all the keys in a map are atoms, you can use the keyword syntax. iex> map = %{a: 1, b: 2, c: 3} %{a: 1, b: 2, c: 3} iex> map.a 1 iex> map.d ** (KeyError) key :d not found in: %{a: 1, b: 2, c: 3} iex> %{map | c: 5} %{a: 1, b: 2, c: 5} iex> %{map | d: 0} ** (KeyError) key :d not found in: %{a: 1, b: 2, c: 3}
  • 20. Structs Structs are extensions built on top of maps that provide compile-time checks and default values. iex> defmodule User do ...> defstruct name: "Ivan", age: 25 ...> end iex> %User{} %User{age: 25, name: "Ivan"}
  • 21. Structs iex> %User{name: "Maria"} %User{age: 25, name: "Maria"} iex> %User{other: "Something"} ** (KeyError) key :other not found in: %User{age: 25, name: "Ivan"}
  • 22. Structs are bare maps underneath, but none of the protocols implemented for maps are available for structs iex> ivan = %User{} %User{age: 25, name: "Ivan"} iex> is_map(ivan) true iex> Map.keys(ivan) [:__struct__, :age, :name] iex> ivan.__struct__ User iex> ivan[:age] ** (UndefinedFunctionError) function User.fetch/2 is undefined (User does not implement the Access behaviour)
  • 23. Pattern matching iex> {a, b, c} = {:hello, "world", 42} {:hello, "world", 42} iex> a :hello iex> b "world" iex> c 42
  • 24. A pattern match will error if the sides can’t be matched iex> {a, b, c} = {:hello, "world"} ** (MatchError) no match of right hand side value: {:hello, "world"} iex> {a, b, c} = [:hello, "world", 42] ** (MatchError) no match of right hand side value: [:hello, "world", 42]
  • 25. We can match on specific values iex> {:ok, result} = {:ok, 13} {:ok, 13} iex> result 13 iex> {:ok, result} = {:error, "Not Found!"} ** (MatchError) no match of right hand side value: {:error, "Not Found!"}
  • 26. We can match on specific values post = Repo.get!(Post, 42) case Repo.delete post do {:ok, struct} -> # Deleted with success {:error, changeset} -> # Something went wrong end
  • 27. Pattern match on lists iex> [a, b, c] = [1, 2, 3] [1, 2, 3] iex> b 2 iex> [head | tail] = [1, 2, 3] [1, 2, 3] iex> head 1 iex> tail [2, 3] iex> [] = [1, 2, 3] ** (MatchError) no match of right hand side value: [1, 2, 3]
  • 28. Pattern match on keyword lists iex> [a: a] = [a: 1] [a: 1] iex> [a: a] = [a: 1, b: 2] ** (MatchError) no match of right hand side value: [a: 1, b: 2] iex> [b: b, a: a] = [a: 1, b: 2] ** (MatchError) no match of right hand side value: [a: 1, b: 2]
  • 29. Pattern match on maps iex> %{} = %{a: 1, b: 2} %{a: 1, b: 2} iex> %{b: b} = %{a: 1, b: 2} %{a: 1, b: 2} iex> b 2 iex> %{c: c} = %{a: 1, b: 2} ** (MatchError) no match of right hand side value: %{a: 1, b: 2}
  • 30. The pin ^ operator and _ iex> x = 2 2 iex> {1, ^x} = {1, 2} {1, 2} iex> {a, _} = {1, 2} {1, 2} iex> a 1
  • 31. Ecto Ecto is split into 4 main components: • Ecto.Repo - repositories are wrappers around the data store. • Ecto.Schema - schemas are used to map any data source into an Elixir struct. • Ecto.Changeset - allow developers to filter, cast, and validate changes before we apply them to the data. • Ecto.Query - written in Elixir syntax, queries are used to retrieve information from a given repository.
  • 32. Ecto playground Ecto in not an ORM github.com/yuriibodarev/Ecto_not_ORM Requires: PostgreSQL Run within IEx console: iex -S mix
  • 33. Repositories Via the repository, we can create, update, destroy and query existing database entries.
  • 34. Repositories Ecto.Repo is a wrapper around the database. We can define a repository as follows (libblogrepo.ex): defmodule Blog.Repo do use Ecto.Repo, otp_app: :blog end
  • 35. Repositories A repository needs an adapter and credentials to communicate to the database. Configuration for the Repo usually defined in your config/config.exs: config :blog, Blog.Repo, adapter: Ecto.Adapters.Postgres, database: "blog_repo", username: "postgres", password: "postgres", hostname: "localhost"
  • 36. Repositories Each repository in Ecto defines a start_link/0. Usually this function is invoked as part of your application supervision tree (libblog.ex): def start(_type, _args) do import Supervisor.Spec, warn: false children = [ worker(Blog.Repo, []), ] opts = [strategy: :one_for_one, name: Blog.Supervisor] Supervisor.start_link(children, opts) end
  • 37. Schema Schemas allows developers to define the shape of their data. (libbloguser.ex) defmodule Blog.User do use Ecto.Schema schema "users" do field :name, :string field :reputation, :integer, default: 0 has_many :posts, Blog.Post, on_delete: :delete_all timestamps end end
  • 38. Schema By defining a schema, Ecto automatically defines a struct: iex> user = %Blog.User{name: "Bill"} %Blog.User{__meta__: #Ecto.Schema.Metadata<:built, "users">, id: nil, inserted_at: nil, name: "Bill"}, posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, reputation: 0, updated_at: nil}
  • 39. Schema Using Schema we can interact with a repository: iex> user = %Blog.User{name: "Bill", reputation: 10} %Blog.User{…} iex> Blog.Repo.insert!(user) %Blog.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 6, inserted_at: ~N[2016-12-13 16:16:35.983000], name: "Bill", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, reputation: 10, updated_at: ~N[2016-12-13 16:16:36.001000]}
  • 40. Schema # Get the user back iex> newuser = Blog.Repo.get(Blog.User, 6) iex> newuser.id 6 # Delete it iex> Blog.Repo.delete(newuser) {:ok, %Blog.User{…, id: 6,…}}
  • 41. Schema We can use pattern matching on Structs created with Schemas: iex> %{name: name, reputation: reputation} = ...> Blog.Repo.get(Blog.User, 1) iex> name "Alex" iex> reputation 144
  • 42. Changesets We can add changesets to our schemas to validate changes before we apply them to the data (libbloguser.ex): def changeset(user, params %{}) do user |> cast(params, [:name, :reputation]) |> validate_required([:name, :reputation]) |> validate_inclusion(:reputation, -999..999) end
  • 43. Changesets iex> alina = %Blog.User{name: "Alina"} iex> correct_changeset = Blog.User.changeset(alina, %{reputation: 55}) #Ecto.Changeset<action: nil, changes: %{reputation: 55}, errors: [], data: #Blog.User<>, valid?: true> iex> invalid_changeset = Blog.User.changeset(alina, %{reputation: 1055}) #Ecto.Changeset<action: nil, changes: %{reputation: 1055}, errors: [reputation: {"is invalid", [validation: :inclusion]}], data: #Blog.User<>, valid?: false>
  • 44. Changeset with Repository functions iex> valid_changeset.valid? true iex> Blog.Repo.insert(valid_changeset) {:ok, %Blog.User{…, id: 7, …}}
  • 45. Changeset with Repository functions iex> invalid_changeset.valid? false iex> Blog.Repo.insert(invalid_changeset) {:error, #Ecto.Changeset<action: :insert, changes: %{reputation: 1055}, errors: [reputation: {"is invalid", [validation: :inclusion]}], data: #Blog.User<>, valid?: false>}
  • 46. Changeset with Repository functions case Blog.Repo.update(changeset) do {:ok, user} -> # user updated {:error, changeset} -> # an error occurred end
  • 47. We can provide different changeset functions for different use cases def registration_changeset(user, params) do # Changeset on create end def update_changeset(user, params) do # Changeset on update end
  • 48. Query Ecto allows you to write queries in Elixir and send them to the repository, which translates them to the underlying database.
  • 49. Query using predefined Schema # Query using predefined Schema query = from u in User, where: u.reputation > 35, select: u # Returns %User{} structs matching the query Repo.all(query) [%Blog.User{…, id: 2, …, name: "Bender", …, reputation: 42, …}, %Blog.User{…, id: 1, …, name: "Alex", …, reputation: 144, …}]
  • 50. Directly querying the “users” table # Directly querying the “users” table query = from u in "users", where: u.reputation > 30, select: %{name: u.name, reputation: u.reputation} # Returns maps as defined in select Repo.all(query) [%{name: "Bender", reputation: 42}, %{name: "Alex", reputation: 144}]
  • 51. External values in Queries # ^ operator min = 33 query = from u in "users", where: u.reputation > ^min, select: u.name # casting mins = "33" query = from u in "users", where: u.reputation > type(^mins, :integer), select: u.name
  • 52. External values in Queries If query is created with predefined Schema than Ecto will automatically cast external value min = "35" Repo.all(from u in User, where: u.reputation > ^min) You can also skip Select to retrieve all fields specified in the Schema
  • 53. Associations schema "users" do field :name, :string field :reputation, :integer, default: 0 has_many :posts, Blog.Post, on_delete: :delete_all timestamps end
  • 54. Associations alex = Repo.get_by(User, name: "Alex") %Blog.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 1, inserted_at: ~N[2016-12-17 06:36:54.916000], name: "Alex", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, reputation: 13, updated_at: ~N[2016-12-17 06:36:54.923000]}
  • 55. Associations alex_wposts = Repo.preload(alex, :posts) %Blog.User{…, id: 1, name: "Alex", posts: [%Blog.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "World", comments: #Ecto.Association.NotLoaded<association :comments is not loaded>, id: 1, inserted_at: ~N[2016-12-17 06:36:54.968000], pinned: true, title: "Hello", updated_at: ~N[2016-12-17 06:36:54.968000], user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: 1}], …}
  • 56. Associations users_wposts = from u in User, where: u.reputation > 10, order_by: [asc: u.name], preload: [:posts]
  • 57. Queries Composition alex = Repo.get_by(User, name: "Alex") alex_post = from p in Post, where: p.user_id == ^alex.id alex_pin = from ap in alex_post, where: ap.pinned == true
  • 58. Fragments title = "hello" from(p in Post, where: fragment("lower(?)", p.title) == ^title) |> Repo.all()