diff --git a/lib/ecto.ex b/lib/ecto.ex index b79bc3eaf9..b6241a0880 100644 --- a/lib/ecto.ex +++ b/lib/ecto.ex @@ -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 diff --git a/lib/ecto/repo/schema.ex b/lib/ecto/repo/schema.ex index 6a910bb78e..a2bb837b5b 100644 --- a/lib/ecto/repo/schema.ex +++ b/lib/ecto/repo/schema.ex @@ -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), @@ -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 diff --git a/lib/ecto/schema/loader.ex b/lib/ecto/schema/loader.ex index b8e4f8fa17..d36d797e3d 100644 --- a/lib/ecto/schema/loader.ex +++ b/lib/ecto/schema/loader.ex @@ -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 diff --git a/lib/ecto/type.ex b/lib/ecto/type.ex index 20783b4261..2109455818 100644 --- a/lib/ecto/type.ex +++ b/lib/ecto/type.ex @@ -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} @@ -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 -> @@ -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 diff --git a/test/ecto/embedded_test.exs b/test/ecto/embedded_test.exs index 3ed4a21f15..fd53f212b0 100644 --- a/test/ecto/embedded_test.exs +++ b/test/ecto/embedded_test.exs @@ -4,10 +4,18 @@ 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 @@ -15,10 +23,10 @@ defmodule Ecto.EmbeddedTest do 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 @@ -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