github.com/darinwilson/music_db_workshop
hexdocs.pm/ecto
Repo
Query
Schema
Changeset
Multi
artist = Artist.get(1)
artist.name = "Miles Davis"
artist.save
Centralized class/module connects to DB
Database operations sent to the Repository
artist = Repo.get(Artist, 1)
changeset =
Ecto.Changeset.change(artist, name: "Miles Davis")
Repo.update(changeset)
artist = Repo.get(Artist, 1)
changeset =
Ecto.Changeset.change(artist,
name: "Miles Davis")
Repo.update(changeset)
artist = Artist.get(1)
artist.name = "Miles Davis"
artist.save
artist = Repo.get(Artist, 1)
changeset =
Ecto.Changeset.change(artist,
name: "Miles Davis")
Repo.update(changeset)
artist = Artist.get(1)
artist.name = "Miles Davis"
artist.save
artist = Repo.get(Artist, 1)
changeset =
Ecto.Changeset.change(artist,
name: "Miles Davis")
Repo.update(changeset)
artist = Artist.get(1)
artist.name = "Miles Davis"
artist.save
# lib/my_app/repo.ex
defmodule MyApp.Repo
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
end
# lib/my_app/repo.ex
defmodule MyApp.Repo
use Ecto.Repo,
otp_app: :my_app
adapter: Ecto.Adapters.Postgres,
end
# config/config.exs
config :my_app, MyApp.Repo,
database: "my_app_db",
username: "postgres",
password: "postgres",
hostname: "localhost"
insert_all
update_all
delete_all
all
alias MyApp.Repo
Repo.insert_all("artists", [[name: "John Coltrane"]])
=> {1, nil}
Repo.insert_all("artists", [[name: "John Coltrane"]])
=> {1, nil}
Repo.insert_all("artists", [[name: "John Coltrane"],
[name: "Max Roach"]])
=> {2, nil}
Repo.insert_all("artists", [[name: "John Coltrane"]])
=> {1, nil}
Repo.insert_all("artists", [[name: "John Coltrane"],
[name: "Max Roach"]])
=> {2, nil}
Repo.insert_all("artists", [%{name: "John Coltrane"},
%{name: "Max Roach"}])
=> {2, nil}
Repo.insert_all("artists", [[name: "John Coltrane"]],
returning: [:id, :name])
Repo.insert_all("artists", [[name: "John Coltrane"]],
returning: [:id, :name])
=> {1, [%{id: 4, name: "John Coltrane"}]}
Repo.update_all("artists",
set: [updated_at: Ecto.DateTime.utc])
=> {75, nil}
Repo.delete_all("artists")
=> {75, nil}
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}}]]}}
# get the highest id of a table
Repo.aggregate("artists", :max, :id)
#=> 3
defmodule MusicDB.Repo do
use Ecto.Repo, otp_app: :music_db
def max_id(table_name) do
aggregate(table_name, :max, :id)
end
end
defmodule MusicDB.Repo do
use Ecto.Repo, otp_app: :music_db
def max_id(table_name) do
aggregate(table_name, :max, :id)
end
end
# now we can do:
Repo.max_id("artists")
https://bit.ly/2ZovyXZ
Repo.insert_all("artists", [[name: "John Coltrane"]])
Repo.update_all("artists",
set: [updated_at: Ecto.DateTime.utc])
Repo.delete_all("artists")
SELECT t.id, t.title, a.title
FROM tracks t
JOIN albums a ON t.album_id = a.id
WHERE t.duration > 900;
import Ecto.Query
query = from t in "tracks",
join: a in "albums", on: t.album_id == a.id,
where: t.duration > 900,
select: [t.id, t.title, a.title]
SELECT t.id, t.title, a.title
FROM tracks t
JOIN albums a ON t.album_id = a.id
WHERE t.duration > 900;
import Ecto.Query
query =
"tracks"
|> join(:inner, [t], a in "albums", t.album_id == a.id)
|> where([t,a], t.duration > 900)
|> select([t,a], [t.id, t.title, a.title])
query = from "artists", select: [:name]
Ecto.Adapters.SQL.to_sql(:all, Repo, query)
#=> {"SELECT a0.\"name\" FROM \"artists\" AS a0", []}
query = from "artists", select: [:name]
Repo.all(query)
#=> [["Miles Davis"], ["Bill Evans"], ["Bobby Hutcherson"]]
q = from "artists",
where: [name: "Bill Evans"],
select: [:id, :name]
Repo.all(q)
#=> [[2, "Bill Evans"]]
# What if we want to dynamically set the name?
artist_name = "Taylor Swift"
q = from "artists",
where: [name: ^artist_name],
select: [:id, :name]
# This doesn't work
q = from "artists", where: name == "Bill Evans", select: [:id, :name]
# Now this works :)
q = from a in "artists", where: a.name == "Bill Evans", select: [:id, :name]
# like statements
q = from a in "artists", where: like(a.name, "Miles%"), select: [:id, :name]
# checking for null
q = from a in "artists", where: is_nil(a.name), select: [:id, :name]
# checking for not null
q = from a in "artists", where: not is_nil(a.name), select: [:id, :name]
# date comparision - this finds artists added more than 1 year ago
q = from a in "artists", where: a.inserted_at < ago(1, "year"), select: [:id, :name]
q = from a in "artists",
where: fragment("lower(?)", a.name) == "miles davis", select: [:id, :name]
Ecto.Adapters.SQL.to_sql(:all, Repo, q)
#=> {"SELECT a0.\"id\", a0.\"name\" FROM \"artists\" AS a0
#=> WHERE (lower(a0.\"name\") = 'miles davis')",
# Select all artists names ordered alphabetically
q = from a in "artists", select: [a.name], order_by: a.name
Repo.all(q)
#=> [["Bill Evans"], ["Bobby Hutcherson"], ["Miles Davis"]]
# Order by results descending.
q = from a in "artists", select: [a.name], order_by: [desc: a.name]
Repo.all(q)
#=> [["Miles Davis"], ["Bobby Hutcherson"], ["Bill Evans"]]
# Grouping tracks by album and summing their times played
q = from t in "tracks", select: [t.album_id, sum(t.number_of_plays)],
group_by: t.album_id
Repo.all(q)
#=> [[4, 2540], [1, 2619], [5, 3057], [3, 2528], [2, 4491]]
# Select all tracks that have a duration longer than 15 minutes.
q = from t in "tracks",
join: a in "albums", on: t.album_id == a.id,
where: t.duration > 900,
select: [a.title, t.title]
Repo.all(q)
#=> [["Cookin' At The Plugged Nickel", "No Blues"],
#=> ["Cookin' At The Plugged Nickel", "If I Were A Bell"]]
# You can use the join keyword more than once to join in multiple tables!
q = from t in "tracks",
join: a in "albums", on: t.album_id == a.id,
join: ar in "artists", on: a.artist_id == ar.id,
where: t.duration > 900,
select: %{album: a.title, track: t.title, artist: ar.name}
Repo.all(q)
#=> [%{album: "Cookin' At The Plugged Nickel", artist: "Miles Davis",
#=> track: "If I Were A Bell"},
#=> %{album: "Cookin' At The Plugged Nickel", artist: "Miles Davis",
#=> track: "No Blues"},
# First query to get all albums by Miles Davis.
q = from a in "albums",
join: ar in "artists", on: a.artist_id == ar.id,
where: ar.name == "Miles Davis",
select: [a.title]
Repo.all(q)
#=> [["Cookin' At The Plugged Nickel"], ["Kind Of Blue"]]
# Then we want to list all the tracks on Miles Davis's albums
q = from a in "albums",
join: ar in "artists", on: a.artist_id == ar.id,
join: t in "tracks", on: t.album_id == a.id,
where: ar.name == "Miles Davis",
select: [t.title]
# Reuse the first query!
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,ar] in albums_by_miles,
join: t in "tracks", on: t.album_id == a.id,
select: [t.title]
# You can also use functions to compose queries.
def albums_by_artist(artist_name) do
from a in "albums",
join: ar in "artists", on: a.artist_id == ar.id,
where: ar.name == ^artist_name
end
def with_tracks_longer_than(query, duration) do
from a in query,
join: t in "tracks", on: t.album_id == a.id,
where: t.duration > ^duration,
distinct: true
end
q =
albums_by_artist("Taylor Swift")
|> with_tracks_longer_than(720)
# Update tracks with the title of "Autum Leaves"
q = from t in "tracks", where: t.title == "Autum Leaves"
Repo.update_all(q, set: [title: "Autumn Leaves"])
#=> [{1, nil}]
# Delete all tracks with the title of "Autum Leaves"
from(t in "tracks", where: t.title == "Autum Leaves")
|> Repo.delete_all()
# You can create an elixir struct for tracks separate from Ecto like so:
defmodule MusicDB.Track do
defstruct [:id, :title, :duration, :index, :number_of_plays]
end
# With Ecto, you use the schema macro to define your struct:
defmodule MusicDB.Track do
use Ecto.Schema
schema "tracks" do
field :title, :string
field :duration, :integer
field :index, :integer
field :number_of_plays, :integer
timestamps()
end
end
# We can use %MusicDB.Track{} to create new structs from the above definition.
Ecto Type | Elixir Type |
---|---|
:id | integer |
:binary_id | binary |
:integer | integer |
:float | float |
:boolean | boolean |
:string | UTF-8 encoded string |
:binary | binary |
{:array, inner_type} | list |
:map | map |
{:map, inner_type} | map |
:decimal | Decimal |
:date | Date |
:time | Time |
:naive_datetime | NaiveDateTime |
:utc_datetime | DateTime |
# Schema-less Query
q = from "artists", where: [name: "Taylor Swift"], select: [:name]
Repo.all(q)
#=> [["Taylor Swift"]]
# Converted to use the Artist schema
q = from Artist, where: [name: "Taylor Swift"]
Repo.all(q)
#=> [%Artist{...}]
# You no longer need to include the select - results will be loaded into %Artist{} structs.
# Schema-less insert
Repo.insert_all("artists", [[name: "John Coltrane"]])
#=> {1, nil}
# Inserting a schema
Repo.insert(%Artist{name: "John Coltrane"})
#=> {:ok, %MusicDB.Artist{__meta__: #Ecto.Schema.Metadata<:loaded, "artists">,
#=> id: 4, name: "John Coltrane", ...}
# Schema-less deletion
from(t in "tracks", where: t.title == "Autum Leaves")
|> Repo.delete_all
# Deleting a schema
track = Repo.get_by(Track, title: "Autum Leaves")
Repo.delete(track)
#=> {:ok, %MusicDB.Track{__meta__: #Ecto.Schema.Metadata<:deleted, "tracks">, #=> id: 28, title:
defmodule MusicDB.Album do
use Ecto.Schema
schema "albums" do
field :title, :string
field :release_date, :date
has_many :tracks, MusicDB.Track
end
end
schema "tracks" do
field :title, :string
# other fields here...
belongs_to :album, MusicDB.Album
end
# in album.ex
schema "albums" do
# field definitions here...
has_many :tracks, MusicDB.Track
belongs_to :artist, MusicDB.Artist
end
# in artist.ex
schema "artists" do
# field definitions here...
has_many :albums, MusicDB.Album
end
schema "artists" do
# field definitions here...
has_many :albums, MusicDB.Album
has_many :tracks, through: [:albums, :tracks]
end
# in album.ex
schema "albums" do
# field definitions here...
many_to_many :genres, MusicDB.Genre, join_through: MusicDB.AlbumGenre
end
# in genre.ex
schema "genres" do
# field definitions here...
many_to_many :albums, MusicDB.Album, join_through: MusicDB.AlbumGenre
end
# in album_genre.ex
schema "albums_genres" do
# field definitions here...
belongs_to :albums, MusicDB.Album
belongs_to :genres, MusicDB.Genre
end
# in album.ex
schema "albums" do
# field definitions here...
many_to_many :genres, MusicDB.Genre, join_through: "albums_genres"
end
# in genre.ex
schema "genres" do
# field definitions here...
many_to_many :albums, MusicDB.Album, join_through: "albums_genres"
end
album = Repo.one(
from a in Album,
where: a.title == "Kind Of Blue"
)
album = Repo.one(
from a in Album,
where: a.title == "Kind Of Blue"
)
album.tracks
album = Repo.one(
from a in Album,
where: a.title == "Kind Of Blue"
)
album.tracks
#Ecto.Association.NotLoaded<association :tracks is
# not loaded>
album = Repo.one(
from a in Album,
where: a.title == "Kind Of Blue"
)
album = Repo.one(
from a in Album,
where: a.title == "Kind Of Blue",
preload: :tracks
)
album = Repo.one(
from a in Album,
where: a.title == "Kind Of Blue",
) |> Repo.preload(:tracks)
album = Repo.one(
from a in Album,
where: a.title == "Kind Of Blue",
)
tracks_query = Ecto.assoc(album, :tracks)
tracks = Repo.all(tracks_query)
albums = Repo.all(
from a in Album,
where: a.artist_id == 1,
)
tracks_query = Ecto.assoc(albums, :tracks)
tracks = Repo.all(tracks_query)
# in album.ex
schema "albums" do
# field definitions here...
has_many :tracks, MusicDB.Track
end
# in track.ex
schema "tracks" do
# field definitions here...
belongs_to :album, MusicDB.Album
end
# in album.ex
schema "albums" do
# field definitions here...
has_many :tracks, MusicDB.Track, on_delete: :delete_all
end
# in track.ex
schema "tracks" do
# field definitions here...
belongs_to :album, MusicDB.Album
end
Repo.insert_all("artists", [[name: "John Coltrane"]])
#=> {1, nil}
Repo.insert_all("artists", [[name: "John Coltrane"]])
#=> {1, nil}
Repo.insert(%Artist{name: "John Coltrane"})
#=> {:ok, %MusicDB.Artist{ ... }}
Repo.insert_all("artists", [[name: "John Coltrane"]])
#=> {1, nil}
Repo.insert(%Artist{name: "John Coltrane"})
#=> {:ok, %MusicDB.Artist{ ... }}
Repo.insert!(%Artist{name: "John Coltrane"})
#=> %MusicDB.Artist{ ... }
Repo.insert(
%Artist{
name: "John Coltrane",
albums: [
%Album{
title: "A Love Supreme"
}
]
}
)
Repo.insert(
%Artist{
name: "John Coltrane",
albums: [
%Album{
title: "A Love Supreme",
tracks: [
%Track{title: "Part 1: Acknowledgement", index: 1},
%Track{title: "Part 2: Resolution", index: 2},
%Track{title: "Part 3: Pursuance", index: 3},
%Track{title: "Part 4: Psalm", index: 4},
],
genres: [
%Genre{name: "spiritual jazz"},
]
}
]
}
)
schema "albums" do
...
has_many :tracks, MusicDB.Track, on_delete: :delete_all
belongs_to :artist, MusicDB.Artist
end
Repo.insert(
%Artist{
name: "John Coltrane",
albums: [
%Album{
title: "A Love Supreme"
}
]
}
)
albums = Repo.all(from a in Album, preload: :tracks)
import Ecto.Changeset
params = %{name: "Gene Harris"}
changeset =
%Artist{}
|> cast(params, [:name])
|> validate_required([:name])
case Repo.insert(changeset) do
{:ok, artist} ->
IO.puts("Record for #{artist.name} was created.")
{:error, changeset} -> IO.inspect(changeset.errors)
end
# internal data
artist = %Artist{name: "Charlie Parker"}
# external data
params = %{"name" => "Charlie Parker"}
import Ecto.Changeset
changeset = change(%Artist{name: "Charlie Parker"})
artist = Repo.get_by(Artist, name: "Bobby Hutcherson")
changeset = change(artist)
artist = Repo.get_by(Artist, name: "Bobby Hutcherson")
changeset = change(artist, name: "Robert Hutcherson")
changeset.changes
#=> %{name: "Robert Hutcherson"}
artist = Repo.get_by(Artist, name: "Bobby Hutcherson")
changeset =
artist
|> change(name: "Robert Hutcherson")
|> change(birth_date: ~D[1941-01-27])
changeset.changes
#=> %{name: "Robert Hutcherson",
# birth_date: ~D[1941-01-27]}
changeset = change(artist, name: "Robert Hutcherson",
birth_date: ~D[1941-01-27])
changeset.changes
#=> %{name: "Robert Hutcherson",
# birth_date: ~D[1941-01-27]}
params = %{"name" => "Charlie Parker",
"birth_date" => "1920-08-29",
"instrument" => "alto sax"}
changeset = cast(%Artist{}, params, [:name, :birth_date])
changeset.changes
#=> %{birth_date: ~D[1920-08-29], name: "Charlie Parker"}
params = %{"name" => "Thelonius Monk",
"birth_date" => "1917-10-10"}
changeset =
%Artist{}
|> cast(params, [:name, :birth_date])
|> validate_required([:name, :birth_date])
|> validate_length(:name, min: 3)
changeset.valid?
#=> true
params = %{"name" => "x"}
changeset =
%Artist{}
|> cast(params, [:name, :birth_date])
|> validate_required([:name, :birth_date])
|> validate_length(:name, min: 3)
changeset.errors
#=> [name: {"should be at least %{count} character(s)",
#=> [count: 3, validation: :length, min: 3]},
#=> birth_date: {"can't be blank", [validation: :required]}]
params = %{"name" => "Thelonius Monk",
"birth_date" => "2117-10-10"}
changeset =
%Artist{}
|> cast(params, [:name, :birth_date])
|> validate_change(:birth_date, fn :birth_date, birth_date ->
cond do
is_nil(birth_date) -> []
Date.compare(birth_date, Date.utc_today()) == :lt -> []
true -> [birth_date: "must be in the past"]
end
end)
changeset.errors
#=> [birth_date: {"must be in the past", []}]
def validate_in_the_past(changeset, field) do
validate_change(changeset, field, fn _field, value ->
cond do
is_nil(value) -> []
Date.compare(value, Date.utc_today()) == :lt -> []
true -> [{field, "must be in the past"}]
end
end)
end
params = %{"name" => "Thelonius Monk",
"birth_date" => "2117-10-10"}
changeset =
%Artist{}
|> cast(params, [:name, :birth_date])
|> validate_required(:name)
|> validate_in_the_past(:birth_date)
changeset.errors
params = %{name: "Gene Harris"}
changeset =
%Artist{}
|> cast(params, [:name])
|> validate_required([:name])
case Repo.insert(changeset) do
{:ok, artist} -> IO.puts("Record created.")
{:error, changeset} -> IO.inspect(changeset.errors)
end
#=> "Record created."
params = %{name: nil}
changeset =
%Artist{}
|> cast(params, [:name])
|> validate_required([:name])
case Repo.insert(changeset) do
{:ok, artist} -> IO.puts("Record created.")
{:error, changeset} -> IO.inspect(changeset.errors)
end
#=> [name: {"can't be blank", [validation: :required]}]
defmodule MusicDB.Artist do
schema "artists" do
...
end
def changeset(artist, params) do
artist
|> cast(params, [:name, :birth_date, :death_date])
|> validate_required([:name])
end
end
artist = Repo.get_by(Artist, name: "Miles Davis")
new_album = Ecto.build_assoc(artist, :albums)
#=> %MusicDB.Album{artist_id: 1, ...}
artist = Repo.get_by(Artist, name: "Miles Davis")
new_album = Ecto.build_assoc(artist, :albums,
title: "Miles Ahead")
#=> %MusicDB.Album{artist_id: 1,
# title: "Miles Ahead", ...}
albums = [%Album{title: "Made In Brazil"},
%Album{title: "Dance Of Time"}]
%Artist{name: "Eliane Elias"}
|> change
|> put_assoc(:albums, albums)
|> Repo.insert
params = %{name: "Eliane Elias", albums: [
%{title: "Made In Brazil"}, %{title: "Dance Of Time"}
]}
%Artist{}
|> cast(params, [:name])
|> cast_assoc(:albums)
|> Repo.insert
params = %{name: "Eliane Elias", albums: [
%{title: "Made In Brazil"}, %{title: "Dance Of Time"}
]}
%Artist{}
|> cast(params, [:name])
|> cast_assoc(:albums)
|> Repo.insert
# must have "albums" field in params
# must have Album.changeset function
Repo.get_by(Artist, name: "Miles Davis")
|> change
|> put_assoc(:albums, [%Album{title: "Miles Ahead"}])
|> Repo.update()
Repo.get_by(Artist, name: "Miles Davis")
|> change
|> put_assoc(:albums, [%Album{title: "Miles Ahead"}])
|> Repo.update()
#=> ** (RuntimeError) attempting to cast or change
#=> association `albums` from `MusicDB.Artist` that
#=> was not loaded. Please preload your associations
#=> before manipulating them through changesets
Repo.get_by(Artist, name: "Miles Davis")
|> Repo.preload(:albums)
|> change
|> put_assoc(:albums, [%Album{title: "Miles Ahead"}])
|> Repo.update()
Repo.get_by(Artist, name: "Miles Davis")
|> Repo.preload(:albums)
|> change
|> put_assoc(:albums, [%Album{title: "Miles Ahead"}])
|> Repo.update()
#=> ** (RuntimeError) you are attempting to change
#=> relation :albums of MusicDB.Artist but the
#=> `:on_replace` option of this relation is set
#=> to `:raise`...
defmodule MusicDB.Artist do
schema "artists" do
...
has_many :albums, Album
end
...
end
defmodule MusicDB.Artist do
schema "artists" do
...
has_many :albums, Album, on_replace: :nilify
end
...
end
Repo.get_by(Artist, name: "Miles Davis")
|> Repo.preload(:albums)
|> change
|> put_assoc(:albums, [%Album{title: "Miles Ahead"}])
|> Repo.update()
#=> {:ok, ... }
import Ecto.Changeset
# casting/filtering
changeset = change(%Artist{name: "Billie Holiday"})
changeset = cast(%Artist{}, %{name: "Billie Holiday"}, [:name])
# validating
%Artist{}
|> cast(params, [:name, :birth_date])
|> validate_required([:name, :birth_date])
# associations
artist = Repo.get_by(Artist, name: "Miles Davis")
new_album = Ecto.build_assoc(artist, :albums)
%Artist{name: "Eliane Elias"}
|> change
|> put_assoc(:albums, [%Album{title: "Made In Brazil"}])
params = %{name: "Eliane Elias", albums: [title: "Made In Brazil"]
%Artist{}
|> cast(params, [:name])
|> cast_assoc(:albums)
|> Repo.insert
album_changeset = Album.changeset(params)
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
Repo.transaction(fn ->
Repo.update!(album_changeset)
Repo.update!(user_changeset)
end)
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
Repo.transaction(fn ->
case Repo.update(album_changeset) do
{:error, _changes} ->
IO.puts "Album update failed"
{:ok, _changes} ->
case Repo.update(user_changeset) do
{:error, _changes} ->
IO.puts "User update failed"
{:ok, _changes} ->
IO.puts "Success"
end
end
end)
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
Multi.new
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
Multi.new
|> Multi.update(:album, album_changeset)
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
Multi.new
|> Multi.update(:album, album_changeset)
|> Multi.update(:user, user_changeset)
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
Multi.new
|> Multi.update(:album, album_changeset)
|> Multi.update(:user, user_changeset)
|> Repo.transaction
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
result =
Multi.new
|> Multi.update(:album, album_changeset)
|> Multi.update(:user, user_changeset)
|> Repo.transaction
case result do
{:ok, _changes} ->
IO.puts "Success!"
{:error, :album, _failed_value, _changes_so_far} ->
IO.puts "Album update failed"
{:error, :user, _failed_value, _changes_so_far} ->
IO.puts "User update failed"
end
def check_user_progress(%{user: user}) do
# count user contributions
# award badge if past threshold
# send email if badge earned
end
album_changeset = Album.changeset(params)
user_changeset = User.add_contribution_changeset()
result =
Multi.new
|> Multi.update(:album, album_changeset)
|> Multi.update(:user, user_changeset)
|> Multi.run(:user_progress, &check_user_progress/1)
|> Repo.transaction
Multi.to_list(multi)
Multi.to_list(multi)
[album: {:update,
#Ecto.Changeset<action: :update,
changes: %{title: "A Love Supreme"},
errors: [], data: #MyApp.Album<>,
valid?: true>, []}
user: {:update,
#Ecto.Changeset<action: :update,
changes: %{edit_count: 151},
errors: [], data: #MyApp.User<>,
valid?: true>, []}
]
by Darin Wilson and Eric Meadows-Jönsson
https://pragprog.com
Discount code: ElixirConf_2019_ecto
(through 9/5)
List Item Example
List Item Example
Normal text example. This content should be a max of two to three sentences per slide. Left-aligned so that it's easier to read.
defmodule MusicDB.Album do
use Ecto.Schema
import Ecto.Changeset
alias MusicDB.{Artist, Track, Genre}
schema "albums" do
field(:title, :string)
timestamps()
belongs_to(:artist, Artist)
has_many(:tracks, Track)
many_to_many(:genres, Genre, join_through: "albums_genres")
has_many :notes, MusicDB.Note
end
end