Skip to content

Commit

Permalink
Refactor embedded load and dump
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Apr 8, 2020
1 parent 3a56308 commit 7ac6e75
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 65 deletions.
13 changes: 5 additions & 8 deletions lib/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -630,18 +630,15 @@ defmodule Ecto do
"""
@spec embedded_load(
module_or_map :: module | map(),
data :: map() | Keyword.t(),
data :: map(),
format :: atom()
) :: Ecto.Schema.t() | map()
def embedded_load(schema_or_types, data, format) do
Ecto.Repo.Schema.embedded_load(schema_or_types, data, format)
Ecto.Schema.Loader.unsafe_load(schema_or_types, data, &Ecto.Type.embedded_load(&1, &2, 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)
@spec embedded_dump(Ecto.Schema.t(), format :: atom()) :: map()
def embedded_dump(%schema{} = data, format) do
Ecto.Schema.Loader.safe_dump(data, schema.__schema__(:dump), &Ecto.Type.embedded_dump(&1, &2, format))
end
end
29 changes: 0 additions & 29 deletions lib/ecto/repo/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -454,16 +454,6 @@ defmodule Ecto.Repo.Schema do
do_load(schema_or_types, data, &Ecto.Type.adapter_load(adapter, &1, &2))
end

def embedded_load(schema_or_types, data, format) do
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),
do: do_load(schema, Map.new(data), loader)
defp do_load(schema, {fields, values}, loader) when is_list(fields) and is_list(values),
Expand All @@ -473,25 +463,6 @@ 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
17 changes: 17 additions & 0 deletions lib/ecto/schema/loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,21 @@ defmodule Ecto.Schema.Loader do
defp error_data(other) when is_map(other) do
""
end

@doc """
Dumps the given data.
"""
def safe_dump(struct, types, dumper) do
Enum.reduce(types, %{}, fn {field, {source, type}}, acc ->
value = Map.get(struct, field)

case dumper.(type, value) do
{:ok, value} ->
Map.put(acc, source, value)
:error ->
raise ArgumentError, "cannot dump `#{inspect value}` as type #{inspect type} " <>
"for field `#{field}` in schema #{inspect struct.__struct__}"
end
end)
end
end
20 changes: 9 additions & 11 deletions lib/ecto/type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ defmodule Ecto.Type do
{:ok, Decimal.new("1")}
"""
def embedded_dump({:embed, _} = type, value, format) do
dump(type, value, &embedded_dump(&1, &2, format))
end

def embedded_dump(type, value, format) do
case embed_as(type, format) do
:self -> {:ok, value}
Expand All @@ -264,6 +268,10 @@ defmodule Ecto.Type do
{:ok, Decimal.new("1")}
"""
def embedded_load({:embed, _} = type, value, format) do
load(type, value, &embedded_load(&1, &2, format))
end

def embedded_load(type, value, format) do
case embed_as(type, format) do
:self ->
Expand Down Expand Up @@ -512,17 +520,7 @@ defmodule Ecto.Type do
end

defp dump_embed(_field, schema, %{__struct__: schema} = struct, types, dumper) do
Enum.reduce(types, %{}, fn {field, {source, type}}, acc ->
value = Map.get(struct, field)

case dumper.(type, value) do
{:ok, value} ->
Map.put(acc, source, value)
:error ->
raise ArgumentError, "cannot dump `#{inspect value}` as type #{inspect type} " <>
"for field `#{field}` in schema #{inspect schema}"
end
end)
Ecto.Schema.Loader.safe_dump(struct, types, dumper)
end

defp dump_embed(field, _schema, value, _types, _fun) do
Expand Down
43 changes: 26 additions & 17 deletions test/ecto/embedded_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@ defmodule Ecto.EmbeddedTest do
doctest Ecto.Embedded
alias Ecto.Embedded

defmodule Post do
use Ecto.Schema

embedded_schema do
field :title
end
end

defmodule Author do
use Ecto.Schema

schema "authors" do
embedded_schema do
field :name, :string
embeds_one :profile, Profile, on_replace: :delete
embeds_one :post, Post
embeds_many :posts, Post, on_replace: :delete
end
end

defmodule MySchemaWithUuid do
defmodule UUIDSchema do
use Ecto.Schema

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

embeds_one :author, Ecto.EmbeddedTest.Author
Expand All @@ -40,37 +48,38 @@ defmodule Ecto.EmbeddedTest do
test "embedded_load/3" do
uuid = Ecto.UUID.generate()

assert %MySchemaWithUuid{uuid: ^uuid} =
Ecto.embedded_load(MySchemaWithUuid, %{"uuid" => uuid}, :json)
assert %UUIDSchema{uuid: ^uuid} =
Ecto.embedded_load(UUIDSchema, %{"uuid" => uuid}, :json)

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

assert %MySchemaWithUuid{uuid: nil} =
Ecto.embedded_load(MySchemaWithUuid, %{"uuid" => nil}, :json)
assert %UUIDSchema{uuid: nil} =
Ecto.embedded_load(UUIDSchema, %{"uuid" => nil}, :json)

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

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

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

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

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

struct = %MySchemaWithUuid{uuid: uuid, authors: [
struct = %UUIDSchema{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
Expand Down

0 comments on commit 7ac6e75

Please sign in to comment.