Top Banner
Leveling Up With Ecto Want to try some of this code? Clone this repo to set up a playground: http://bit.ly/leveling_up ElixirConf 2016
85

Want to try some of this code? Clone this repo to set up a ...

Nov 10, 2021

Download

Documents

dariahiddleston
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Want to try some of this code? Clone this repo to set up a ...

Leveling Up With Ecto

Want to try some of this code? Clone this repo to set up a playground:

http://bit.ly/leveling_up

ElixirConf 2016

Page 2: Want to try some of this code? Clone this repo to set up a ...
Page 3: Want to try some of this code? Clone this repo to set up a ...
Page 4: Want to try some of this code? Clone this repo to set up a ...

SODD

Page 5: Want to try some of this code? Clone this repo to set up a ...

SODD(Stack Overflow Driven Development)

Page 6: Want to try some of this code? Clone this repo to set up a ...
Page 7: Want to try some of this code? Clone this repo to set up a ...
Page 8: Want to try some of this code? Clone this repo to set up a ...
Page 9: Want to try some of this code? Clone this repo to set up a ...

What is Ecto?

Page 10: Want to try some of this code? Clone this repo to set up a ...

Ecto Is...

a toolset for interacting with external datasources

Page 11: Want to try some of this code? Clone this repo to set up a ...
Page 12: Want to try some of this code? Clone this repo to set up a ...

The Big Four

Page 13: Want to try some of this code? Clone this repo to set up a ...

The Big Four

→ Repo→ Query→ Schema→ Changeset

Page 14: Want to try some of this code? Clone this repo to set up a ...

Repo

Page 15: Want to try some of this code? Clone this repo to set up a ...

Repo

The part that actually talks to the datasource

Page 16: Want to try some of this code? Clone this repo to set up a ...

Repo

get()

insert()

update()

delete()

transaction()

Page 17: Want to try some of this code? Clone this repo to set up a ...

Typical use

defmodule MyApp.Repo

use Ecto.Repo, otp_app: :my_app

end

Page 18: Want to try some of this code? Clone this repo to set up a ...

Typical use

defmodule MyApp.MySuperAwesomeDb!!!♥#

use Ecto.Repo, otp_app: :my_app

end

Page 19: Want to try some of this code? Clone this repo to set up a ...

Typical use

defmodule MyApp.Repo

use Ecto.Repo, otp_app: :my_app

end

Page 20: Want to try some of this code? Clone this repo to set up a ...

Typical use

defmodule MyApp.Repo

use Ecto.Repo, otp_app: :my_app

end

config :my_app, MyApp.Repo, adapter: Ecto.Adapters.Postgres, database: "my_app_db", username: "postgres", password: "postgres", hostname: "localhost"

Page 21: Want to try some of this code? Clone this repo to set up a ...

Typical use

defmodule MyApp.Repo use Ecto.Repo, otp_app: :my_append

...alias MyApp.Repo

Repo.insert_all("artists", [[name: "John Coltrane"]])

Page 22: Want to try some of this code? Clone this repo to set up a ...

Connect to another data source

defmodule MyApp.LegacyRepo

use Ecto.Repo, otp_app: :my_app

end

config :my_app, MyApp.LegacyRepo, adapter: Ecto.Adapters.AncientDB, # <- not actually supported! database: "omg_scary_legacy_db", username: "dba1", password: "luvMatchbox20", hostname: "sparc_station7"

Page 23: Want to try some of this code? Clone this repo to set up a ...

Add your own Repo functions

Page 24: Want to try some of this code? Clone this repo to set up a ...

Add your own Repo functions

defmodule MusicDb.Repo do use Ecto.Repo, otp_app: :music_db

def count(table) do aggregate(table, :count, :id) endend...Repo.count("artists")=> 75

Page 25: Want to try some of this code? Clone this repo to set up a ...

Work directly with the data

The *all functions:insert_all

update_all

delete_all

all

Page 26: Want to try some of this code? Clone this repo to set up a ...

Repo.insert_all("artists", [[name: "John Coltrane"]])=> {1, nil}

Repo.update_all("artists", set: [updated_at: Ecto.DateTime.utc])=> {75, nil}

Repo.delete_all("artists")=> {75, nil}

Page 27: Want to try some of this code? Clone this repo to set up a ...

But what about queries?

Page 28: Want to try some of this code? Clone this repo to set up a ...

Low-level queries

Ecto.Adapters.SQL.query(Repo, "select * from artists where id=$1", [1])

=> {:ok, %Postgrex.Result{columns: ["id", "name", "inserted_at", "updated_at"], command: :select, connection_id: 10467, num_rows: 1, rows: [[1, "Miles Davis", {{2016, 8, 25}, {7, 20, 34, 0}}, {{2016, 8, 25}, {7, 20, 34, 0}}]]}}

Page 29: Want to try some of this code? Clone this repo to set up a ...

Query

Page 30: Want to try some of this code? Clone this repo to set up a ...

Feels a lot like SQL (in a good way)

SELECT t.id, t.title, a.title FROM tracks t JOIN albums a ON t.album_id = a.id WHERE t.duration > 600;

query = from t in "tracks", join: a in "albums", on: t.album_id == a.id, where: t.duration > 600, select: [t.id, t.title, a.title]

Page 31: Want to try some of this code? Clone this repo to set up a ...

Feels a lot like SQL (in a good way)

SELECT t.id, t.title, a.title FROM tracks t JOIN albums a ON t.album_id = a.id WHERE t.duration > 600;

query = from t in "tracks", join: a in "albums", on: t.album_id == a.id, where: t.duration > 600, select: [t.id, t.title, a.title]

Repo.all(query)

Page 32: Want to try some of this code? Clone this repo to set up a ...

Use ^ to interpolate

min_duration = 600

query = from t in "tracks", join: a in "albums", on: t.album_id == a.id,➡ where: t.duration > ^min_duration, select: [t.id, t.title, a.title]

Page 33: Want to try some of this code? Clone this repo to set up a ...

Might have to think about types

min_duration = "600"

query = from t in "tracks", join: a in "albums", on: t.album_id == a.id,➡ where: t.duration > type(^min_duration, :integer), select: [t.id, t.title, a.title]

Page 34: Want to try some of this code? Clone this repo to set up a ...

Queries are composable

Page 35: Want to try some of this code? Clone this repo to set up a ...

(you might call them "scopes")

Page 36: Want to try some of this code? Clone this repo to set up a ...

Queries are composable

albums_by_miles = from a in "albums", join: ar in "artists", on: a.artist_id == ar.id, where: ar.name == "Miles Davis"

Page 37: Want to try some of this code? Clone this repo to set up a ...

Queries are composable

albums_by_miles = from a in "albums", join: ar in "artists", on: a.artist_id == ar.id, where: ar.name == "Miles Davis"

query = from a in albums_by_miles, select: a.title

Repo.all(q)# => ["Cookin' At The Plugged Nickel", "Kind Of Blue"]

Page 38: Want to try some of this code? Clone this repo to set up a ...

Queries are composable

albums_by_miles = from a in "albums", join: ar in "artists", on: a.artist_id == ar.id, where: ar.name == "Miles Davis"

q = from a in albums_by_miles, join: t in "tracks", on: a.id == t.album_id, select: t.title

Repo.all(q)# => ["If I Were A Bell", "Stella By Starlight" ...]

Page 39: Want to try some of this code? Clone this repo to set up a ...

Use queries in Repo.*all functions

from a in "artists", where: a.name == "Art Taytum"|> Repo.update_all(set: [name: "Art Tatum"])

from t in "tracks", where: t.duration > 600|> Repo.delete_all

Page 40: Want to try some of this code? Clone this repo to set up a ...

The story so far...

We can do free-form queries with Repo.*all, but:→ we have to be explicit with select→ we have to be careful about types

Page 41: Want to try some of this code? Clone this repo to set up a ...

Schema

Page 42: Want to try some of this code? Clone this repo to set up a ...

Schemas define a reusable shape that we can use to move data in

and out of our data source.

Page 43: Want to try some of this code? Clone this repo to set up a ...

defmodule MusicDb.Track do use Ecto.Schema

schema "tracks" do field :title, :string field :duration, :integer field :index, :integer timestamps endend

Page 44: Want to try some of this code? Clone this repo to set up a ...

# without schemaquery = from t in "tracks", where: t.duration > type(^min_duration, :integer), select: [t.id, t.title, t.duration, t.index]

# with schemaquery = from t in Track, where: t.duration > ^min_duration

Page 45: Want to try some of this code? Clone this repo to set up a ...

But you don't always have to use them

# fetch artist name and number of albumsq = from a in Artist, join: al in Album, on: a.id == al.artist_id, group_by: a.id, select: %{name: a.name, album_count: count(al.id)}

Repo.all(q)# => [%{album_count: 2, name: "Miles Davis"}...]

Page 46: Want to try some of this code? Clone this repo to set up a ...

Schemas are a huge help with associations

Page 47: Want to try some of this code? Clone this repo to set up a ...

defmodule MusicDb.Artist do use Ecto.Schema

schema "artists" do➡ has_many :albums, MusicDb.Album

field :name, :string timestamps endend

Page 48: Want to try some of this code? Clone this repo to set up a ...

Associations

has_many

has_one

belongs_to

many_to_many

Page 49: Want to try some of this code? Clone this repo to set up a ...

defmodule MusicDb.Album do use Ecto.Schema

schema "albums" do ➡ belongs_to :artist, musicdb.artist has_many :tracks, musicdb.track many_to_many :genres, musicdb.genre, join_through: "albums_genres"

field :title, :string timestamps endend

Page 50: Want to try some of this code? Clone this repo to set up a ...

defmodule MusicDb.Album do use Ecto.Schema

schema "albums" do belongs_to :artist, MusicDb.Artist ➡ has_many :tracks, MusicDb.Track many_to_many :genres, MusicDb.Genre, join_through: "albums_genres"

field :title, :string timestamps endend

Page 51: Want to try some of this code? Clone this repo to set up a ...

defmodule MusicDb.Album do use Ecto.Schema

schema "albums" do belongs_to :artist, MusicDb.Artist has_many :tracks, MusicDb.Track ➡ many_to_many :genres, MusicDb.Genre, join_through: "albums_genres"

field :title, :string timestamps endend

Page 52: Want to try some of this code? Clone this repo to set up a ...

Seeing the association records

album = Repo.get(Album, 1)

album.tracks

Page 53: Want to try some of this code? Clone this repo to set up a ...

The dreaded#Ecto.Association.NotLoaded

Page 54: Want to try some of this code? Clone this repo to set up a ...

The dreaded#Ecto.Association.NotLoaded

Page 55: Want to try some of this code? Clone this repo to set up a ...

But why?

Ecto won't fetch associated recordsunless you ask it to

(and this is a good thing)

Page 56: Want to try some of this code? Clone this repo to set up a ...

How to fix it

Use preload in your query# fetch Bobby Hutcherson and all his albumsquery = from a in Artist, where: a.name == "Bobby Hutcherson",➡ preload: [:albums]

Page 57: Want to try some of this code? Clone this repo to set up a ...

How to fix it

(you can nest these)# fetch Bobby Hutcherson and all his albums with all the tracksquery = from a in Artist, where: a.name == "Bobby Hutcherson",➡ preload: [albums: :tracks]

Page 58: Want to try some of this code? Clone this repo to set up a ...

How to fix it

Use Repo.preload after the fact# fetch artist with id 1 and their albumsArtist |> Repo.get(1) |> Repo.preload(:albums)

Page 59: Want to try some of this code? Clone this repo to set up a ...

Inserting new records is a breeze

Repo.insert! %Artist{ name: "Bobby Hutcherson", albums: [ %Album{ title: "Live At Montreaux", tracks: [ %Track{ title: "Anton's Ball", index: 1 }, %Track{ title: "The Moontrane", index: 2 }, %Track{ title: "Farallone", index: 3 }, %Track{ title: "Song Of Songs", index: 4 } ] } ]}

Page 60: Want to try some of this code? Clone this repo to set up a ...

...as long as your data is valid.

Page 61: Want to try some of this code? Clone this repo to set up a ...

Changeset

Page 62: Want to try some of this code? Clone this repo to set up a ...

Changesets allow you to filter, cast, and validate values that you want to send to

your datasource

Page 63: Want to try some of this code? Clone this repo to set up a ...

Filter and cast

import Ecto.Changeset

# %{"title" => "So What", "index" => "1"}params = get_user_input()

changeset = %Track{} |> cast(params, [:title, :index])

Page 64: Want to try some of this code? Clone this repo to set up a ...

Validate

import Ecto.Changeset

# %{"title" => "So What", "index" => "1"}params = get_user_input()

changeset = %Track{} |> cast(params, [:title, :index]) |> validate_required([:title, :index]) |> validate_number(:index, greater_than: 0)

Page 65: Want to try some of this code? Clone this repo to set up a ...

See if it works

case Repo.insert(changeset) do {:ok, track} -> IO.puts "Track #{track.name} succesfully added" {:error, changeset} -> IO.puts changeset.errorsend

Page 66: Want to try some of this code? Clone this repo to set up a ...

Validations

validate_acceptance validate_change validate_confirmation validate_exclusion validate_format validate_inclusion validate_length validate_number validate_required validate_subset

Page 67: Want to try some of this code? Clone this repo to set up a ...

Constraints

assoc_constraint check_constraint exclusion_constraint foreign_key_constraint no_assoc_constraint unique_constraint

Page 68: Want to try some of this code? Clone this repo to set up a ...

Schemaless changesets

Page 69: Want to try some of this code? Clone this repo to set up a ...

Schemaless changesets

types = %{band_name: :string, album_name: :string}params = %{band_name: "Modern Jazz Quartet", album_name: ""}changeset = {%{}, types} |> cast(params, Map.keys(types)) |> validate_required([:band_name, :album_name])

changeset.valid?# => falsechangeset.errors# => [album_name: {"can't be blank", []}]

Page 70: Want to try some of this code? Clone this repo to set up a ...

Phoenix integration

phoenix_ecto

Page 71: Want to try some of this code? Clone this repo to set up a ...

Phoenix integration

= form_for @changeset, @action, fn f ->

.field = label f, :title = text_input f, :title = error_tag f, :title

.field = label f, :index = text_input f, :index = error_tag f, :index...

Page 72: Want to try some of this code? Clone this repo to set up a ...

defmodule MusicDb.Track do use Ecto.Schema

schema "tracks" do belongs_to :album, MusicDb.Album

field :title, :string field :duration, :integer field :index, :integer timestamps endend

Page 73: Want to try some of this code? Clone this repo to set up a ...

defmodule MusicDb.Track do use Ecto.Schema

schema "tracks" do belongs_to :album, MusicDb.Album

field :title, :string field :duration, :integer field :index, :integer ➡ field :duration_string, :string, virtual: true timestamps endend

Page 74: Want to try some of this code? Clone this repo to set up a ...

= form_for @changeset, @action, fn f ->

...

.field = label f, :duration_string = text_input f, :duration_string = error_tag f, :duration_string

...

Page 75: Want to try some of this code? Clone this repo to set up a ...

Validate, then convert

changeset = %Track{} |> cast(params, [:title, :index, :duration_string]) |> validate_required([:title, :index]) |> validate_number(:index, greater_than: 0)

Page 76: Want to try some of this code? Clone this repo to set up a ...

Validate, then convert

changeset = %Track{} |> cast(params, [:title, :index, :duration_string]) |> validate_required([:title, :index]) |> validate_number(:index, greater_than: 0) |> convert_duration_string_to_integer()

Page 77: Want to try some of this code? Clone this repo to set up a ...

Validate, then convert

def convert_duration_string_to_integer(changeset) do duration_string = get_field(changeset, :duration_string) duration_integer = convert_to_integer(duration_string) put_change(changeset, :duration, duration_integer)end

Page 78: Want to try some of this code? Clone this repo to set up a ...

Validate, then convert

import Ecto.Changeset

params = get_user_input()allowed_fields = [:title, :index, :duration_string]changeset = %Track{} |> cast(params, allowed_fields) |> validate_required([:title, :index]) |> validate_number(:index, greater_than: 0) |> convert_duration_string_to_integer() |> validate_number(:duration, greater_than: 0)

Page 79: Want to try some of this code? Clone this repo to set up a ...

Schemas don't have to have a database table

embedded_schema do

...

end

Page 80: Want to try some of this code? Clone this repo to set up a ...

Schemas don't have to have a database table

Ecto's insert_all and schemaless queries

http://blog.plataformatec.com.br/2016/05/ectos-insert_all-and-schemaless-queries/

Page 81: Want to try some of this code? Clone this repo to set up a ...
Page 82: Want to try some of this code? Clone this repo to set up a ...

But what does it all mean?

Page 83: Want to try some of this code? Clone this repo to set up a ...

Flexibility

Page 84: Want to try some of this code? Clone this repo to set up a ...

More leveling up

Github: https://github.com/elixir-ecto/ectoDocumentation: https://hexdocs.pm/ecto/Ecto.html

Page 85: Want to try some of this code? Clone this repo to set up a ...

Thanks

@darinwilson

Photos (from Flickr): abbybatchelder, looka, eleaf,

85552598@N03