Skip to content

Commit

Permalink
Added Ecto.embedded_dump/2 and fix recursion for deep embeds (#3271)
Browse files Browse the repository at this point in the history
  • Loading branch information
narrowtux committed Apr 8, 2020
1 parent fc9061a commit 3a56308
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
8 changes: 8 additions & 0 deletions lib/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -636,4 +636,12 @@ defmodule Ecto do
def embedded_load(schema_or_types, data, format) do
Ecto.Repo.Schema.embedded_load(schema_or_types, data, format)
end

@spec embedded_dump(
Ecto.Schema.t(),
format :: atom()
) :: map()
def embedded_dump(%{__meta__: %Ecto.Schema.Metadata{schema: schema}} = data, format) do
Ecto.Repo.Schema.embedded_dump(schema, data, format)
end
end
27 changes: 26 additions & 1 deletion lib/ecto/repo/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,13 @@ defmodule Ecto.Repo.Schema do
end

def embedded_load(schema_or_types, data, format) do
do_load(schema_or_types, data, &Ecto.Type.embedded_load(&1, &2, format))
do_load(schema_or_types, data, &do_embedded(&1, &2, format, :embedded_load))
end

def embedded_dump(schema, struct, format) do
schema
|> do_load(Map.drop(struct, [:__struct__, :__meta__]), &do_embedded(&1, &2, format, :embedded_dump))
|> Map.drop([:__struct__, :__meta__])
end

defp do_load(schema, data, loader) when is_list(data),
Expand All @@ -467,6 +473,25 @@ defmodule Ecto.Repo.Schema do
defp do_load(types, data, loader) when is_map(types),
do: Ecto.Schema.Loader.unsafe_load(%{}, types, data, loader)


defp do_embedded(_type, nil, _format, _function) do
{:ok, nil}
end
defp do_embedded({:embed, meta}, value, format, function) do
loaded = case meta do
%{cardinality: :one, related: schema} ->
apply(__MODULE__, function, [schema, value, format])

%{cardinality: :many, related: schema} ->
Enum.map(value, &apply(__MODULE__, function, [schema, &1, format]))
end

{:ok, loaded}
end
defp do_embedded(type, value, format, function) do
apply(Ecto.Type, function, [type, value, format])
end

## Helpers

defp returning(schema, opts) do
Expand Down
25 changes: 25 additions & 0 deletions test/ecto/embedded_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ defmodule Ecto.EmbeddedTest do

schema "my_schema" do
field :uuid, Ecto.UUID

embeds_one :author, Ecto.EmbeddedTest.Author
embeds_many :authors, Ecto.EmbeddedTest.Author
end
end

Expand All @@ -46,10 +49,32 @@ defmodule Ecto.EmbeddedTest do
assert %MySchemaWithUuid{uuid: nil} =
Ecto.embedded_load(MySchemaWithUuid, %{"uuid" => nil}, :json)

assert %MySchemaWithUuid{uuid: ^uuid, author: %Author{name: "Bob"}} =
Ecto.embedded_load(MySchemaWithUuid, %{"uuid" => uuid, "author" => %{"name" => "Bob"}}, :json)

assert %MySchemaWithUuid{uuid: ^uuid, authors: [%Author{}]} =
Ecto.embedded_load(MySchemaWithUuid, %{"uuid" => uuid, "authors" => [%{}]}, :json)

assert_raise ArgumentError,
~s[cannot load `"ABC"` as type Ecto.UUID for field `uuid` in schema Ecto.EmbeddedTest.MySchemaWithUuid],
fn ->
Ecto.embedded_load(MySchemaWithUuid, %{"uuid" => "ABC"}, :json)
end
end

test "embedded_dump/2" do
uuid = Ecto.UUID.generate()

assert %{uuid: ^uuid} = Ecto.embedded_dump(%MySchemaWithUuid{uuid: uuid}, :json)

struct = %MySchemaWithUuid{uuid: uuid, authors: [
%Author{name: "Bob"},
%Author{name: "Alice"}
]}
dumped = Ecto.embedded_dump(struct, :json)
assert not Map.has_key?(dumped, :__struct__)
assert [author1 | _] = dumped.authors
assert not Map.has_key?(author1, :__struct__)
assert not Map.has_key?(author1, :__meta__)
end
end

0 comments on commit 3a56308

Please sign in to comment.