Packages
ecto
0.11.0
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/repo/model.ex
defmodule Ecto.Repo.Model do
# The module invoked by user defined repos
# for model related functionality.
@moduledoc false
alias Ecto.Query.Planner
alias Ecto.Model.Callbacks
alias Ecto.Changeset
@doc """
Implementation for `Ecto.Repo.insert/2`.
"""
def insert(repo, adapter, %Changeset{} = changeset, opts) when is_list(opts) do
struct = struct_from_changeset!(changeset)
model = struct.__struct__
fields = model.__schema__(:fields)
source = struct.__meta__.source
return = model.__schema__(:read_after_writes)
# On insert, we always merge the whole struct into the
# changeset as changes, except the primary key if it is nil.
changeset = %{changeset | repo: repo}
changeset = merge_into_changeset(struct, fields, changeset)
with_transactions_if_callbacks repo, adapter, model, opts,
~w(before_insert after_insert)a, fn ->
# Callbacks.__apply__/3 "ignores" undefined callbacks and simply returns
# the changeset unchanged in case the callback is missing.
changeset = Callbacks.__apply__(model, :before_insert, changeset)
changes = validate_changes(:insert, model, fields, changeset)
{:ok, values} = adapter.insert(repo, source, changes, return, opts)
changeset = load_into_changeset(changeset, model, values)
Callbacks.__apply__(model, :after_insert, changeset).model
end
end
def insert(repo, adapter, %{__struct__: _} = struct, opts) do
insert(repo, adapter, %Changeset{model: struct, valid?: true}, opts)
end
@doc """
Implementation for `Ecto.Repo.update/2`.
"""
def update(repo, adapter, %Changeset{} = changeset, opts) when is_list(opts) do
struct = struct_from_changeset!(changeset)
model = struct.__struct__
fields = model.__schema__(:fields)
source = struct.__meta__.source
return = model.__schema__(:read_after_writes)
# Differently from insert, update does not copy the struct
# fields into the changeset. All changes must be in the
# changeset before hand.
changeset = %{changeset | repo: repo}
with_transactions_if_callbacks repo, adapter, model, opts,
~w(before_update after_update)a, fn ->
# Callbacks.__apply__/3 "ignores" undefined callbacks and simply returns
# the changeset unchanged in case the callback is missing.
changeset = Callbacks.__apply__(model, :before_update, changeset)
changes = validate_changes(:update, model, fields, changeset)
filters = add_pk_filter!(changeset.filters, struct)
filters = Planner.fields(:update, model, filters)
values =
if changes != [] do
case adapter.update(repo, source, changes, filters, return, opts) do
{:ok, values} ->
values
{:error, :stale} ->
raise Ecto.StaleModelError, model: struct, action: :update
end
else
[]
end
changeset = load_into_changeset(changeset, model, values)
Callbacks.__apply__(model, :after_update, changeset).model
end
end
def update(repo, adapter, %{__struct__: model} = struct, opts) do
changes = Map.take(struct, model.__schema__(:fields))
# Remove all primary key fields from the list of changes.
changes =
Enum.reduce model.__schema__(:primary_key), changes, &Map.delete(&2, &1)
changeset = %Changeset{model: struct, valid?: true, changes: changes}
update(repo, adapter, changeset, opts)
end
@doc """
Implementation for `Ecto.Repo.delete/2`.
"""
def delete(repo, adapter, %Changeset{} = changeset, opts) when is_list(opts) do
struct = struct_from_changeset!(changeset)
model = struct.__struct__
source = struct.__meta__.source
# There are no field changes on delete
changeset = %{changeset | repo: repo}
with_transactions_if_callbacks repo, adapter, model, opts,
~w(before_delete after_delete)a, fn ->
changeset = Callbacks.__apply__(model, :before_delete, changeset)
filters = add_pk_filter!(changeset.filters, struct)
filters = Planner.fields(:delete, model, filters)
case adapter.delete(repo, source, filters, opts) do
{:ok, _} -> nil
{:error, :stale} ->
raise Ecto.StaleModelError, model: struct, action: :delete
end
model = Callbacks.__apply__(model, :after_delete, changeset).model
put_in model.__meta__.state, :deleted
end
end
def delete(repo, adapter, %{__struct__: _} = struct, opts) do
delete(repo, adapter, %Changeset{model: struct, valid?: true}, opts)
end
## Helpers
defp struct_from_changeset!(%{valid?: false}),
do: raise(ArgumentError, "cannot insert/update an invalid changeset")
defp struct_from_changeset!(%{model: nil}),
do: raise(ArgumentError, "cannot insert/update a changeset without a model")
defp struct_from_changeset!(%{model: struct}),
do: struct
defp load_into_changeset(%{changes: changes} = changeset, model, values) do
update_in changeset.model, &do_load(struct(&1, changes), model, values)
end
defp do_load(struct, model, kv) do
types = model.__changeset__
model = Enum.reduce(kv, struct, fn
{k,v}, acc ->
value = Ecto.Type.load!(Map.fetch!(types, k), v)
Map.put(acc, k, value)
end)
put_in model.__meta__.state, :loaded
end
defp merge_into_changeset(struct, fields, changeset) do
# Get only the database fields from the struct
changes = Map.take(struct, fields)
# Remove nil primary key fields from changes
changes =
Enum.reduce Ecto.Model.primary_key(struct), changes, fn
{k, nil}, acc -> Map.delete(acc, k)
_, acc -> acc
end
update_in changeset.changes, &Map.merge(changes, &1)
end
defp validate_changes(kind, model, fields, changeset) do
Planner.fields(kind, model, Map.take(changeset.changes, fields))
end
defp add_pk_filter!(filters, struct) do
Enum.reduce Ecto.Model.primary_key!(struct), filters, fn
{_k, nil}, _acc ->
raise Ecto.MissingPrimaryKeyError, struct: struct
{k, v}, acc ->
Map.put(acc, k, v)
end
end
defp with_transactions_if_callbacks(repo, adapter, model, opts, callbacks, fun) do
if Enum.any?(callbacks, &function_exported?(model, &1, 1)) do
{:ok, value} = adapter.transaction(repo, opts, fun)
value
else
fun.()
end
end
end