Skip to content

Commit

Permalink
Merge pull request #876 from michalmuskala/relation_get_field
Browse files Browse the repository at this point in the history
get_field/3 and fetch_field/2 for relations return models
  • Loading branch information
josevalim committed Aug 14, 2015
2 parents 49ed0a5 + 527f137 commit 4cef50e
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 4 deletions.
27 changes: 23 additions & 4 deletions lib/ecto/changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,10 @@ defmodule Ecto.Changeset do
then falls back on the model, finally returning `:error` if
no value is available.
For relations this functions will return the models with changes applied,
as if they were taken from model.
To retrieve raw changesets, please use `fetch_change/2`.
## Examples
iex> post = %Post{title: "Foo", body: "Bar baz bong"}
Expand All @@ -568,9 +572,10 @@ defmodule Ecto.Changeset do
"""
@spec fetch_field(t, atom) :: {:changes, term} | {:model, term} | :error
def fetch_field(%{changes: changes, model: model} = _changeset, key) do
def fetch_field(%Changeset{changes: changes, model: model, types: types}, key) do
case Map.fetch(changes, key) do
{:ok, value} -> {:changes, value}
{:ok, value} ->
{:changes, change_as_field(types, key, value)}
:error ->
case Map.fetch(model, key) do
{:ok, value} -> {:model, value}
Expand All @@ -587,6 +592,10 @@ defmodule Ecto.Changeset do
then falls back on the model, finally returning `default` if
no value is available.
For relations this functions will return the models with changes applied,
as if they were taken from model.
To retrieve raw changesets, please use `get_change/3`.
iex> post = %Post{title: "A title", body: "My body is a cage"}
iex> changeset = change(post, %{title: "A new title"})
iex> get_field(changeset, :title)
Expand All @@ -596,9 +605,10 @@ defmodule Ecto.Changeset do
"""
@spec get_field(t, atom, term) :: term
def get_field(%Changeset{changes: changes, model: model} = _changeset, key, default \\ nil) do
def get_field(%Changeset{changes: changes, model: model, types: types}, key, default \\ nil) do
case Map.fetch(changes, key) do
{:ok, value} -> value
{:ok, value} ->
change_as_field(types, key, value)
:error ->
case Map.fetch(model, key) do
{:ok, value} -> value
Expand All @@ -607,6 +617,15 @@ defmodule Ecto.Changeset do
end
end

defp change_as_field(types, key, value) do
case Map.get(types, key) do
{tag, relation} when tag in @relations ->
Relation.apply_changes(relation, value)
_other ->
value
end
end

@doc """
Fetches a change from the given changeset.
Expand Down
19 changes: 19 additions & 0 deletions test/ecto/association_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,25 @@ defmodule Ecto.AssociationTest do
assert %Ecto.Changeset{} = changeset.changes.profile
end

test "get_field/3, fetch_field/2 with assocs" do
profile_changeset = Changeset.change(%Profile{}, name: "michal")
profile = Changeset.apply_changes(profile_changeset)

changeset = Changeset.change(%Author{}, profile: profile_changeset)
assert Changeset.get_field(changeset, :profile) == profile
assert Changeset.fetch_field(changeset, :profile) == {:changes, profile}

changeset = Changeset.change(%Author{profile: profile})
assert Changeset.get_field(changeset, :profile) == profile
assert Changeset.fetch_field(changeset, :profile) == {:model, profile}

post = %Post{id: 1}
post_changeset = %{Changeset.change(post) | action: :delete}
changeset = Changeset.change(%Author{posts: [post]}, posts: [post_changeset])
assert Changeset.get_field(changeset, :posts) == []
assert Changeset.fetch_field(changeset, :posts) == {:changes, []}
end

test "on_replace: :nilify" do
# one case is handled inside repo
post = %Post{id: 1, summary_id: 5}
Expand Down
19 changes: 19 additions & 0 deletions test/ecto/embedded_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,25 @@ defmodule Ecto.EmbeddedTest do
assert %Ecto.Changeset{} = changeset.changes.profile
end

test "get_field/3, fetch_field/2 with embeds" do
profile_changeset = Changeset.change(%Profile{}, name: "michal")
profile = Changeset.apply_changes(profile_changeset)

changeset = Changeset.change(%Author{}, profile: profile_changeset)
assert Changeset.get_field(changeset, :profile) == profile
assert Changeset.fetch_field(changeset, :profile) == {:changes, profile}

changeset = Changeset.change(%Author{profile: profile})
assert Changeset.get_field(changeset, :profile) == profile
assert Changeset.fetch_field(changeset, :profile) == {:model, profile}

post = %Post{id: 1}
post_changeset = %{Changeset.change(post) | action: :delete}
changeset = Changeset.change(%Author{posts: [post]}, posts: [post_changeset])
assert Changeset.get_field(changeset, :posts) == []
assert Changeset.fetch_field(changeset, :posts) == {:changes, []}
end

test "empty" do
assert Relation.empty(%Embedded{cardinality: :one}) == nil
assert Relation.empty(%Embedded{cardinality: :many}) == []
Expand Down

0 comments on commit 4cef50e

Please sign in to comment.