diff --git a/integration_test/cases/repo.exs b/integration_test/cases/repo.exs index 5b3e39360f..ccfd8bbf5f 100644 --- a/integration_test/cases/repo.exs +++ b/integration_test/cases/repo.exs @@ -6,6 +6,7 @@ defmodule Ecto.Integration.RepoTest do alias Ecto.Integration.Post alias Ecto.Integration.Order + alias Ecto.Integration.Item alias Ecto.Integration.User alias Ecto.Integration.Comment alias Ecto.Integration.Permalink @@ -1308,10 +1309,17 @@ defmodule Ecto.Integration.RepoTest do end test "virtual field" do - assert %Post{id: id} = TestRepo.insert!(%Post{title: "1"}) + assert %Post{id: id} = TestRepo.insert!(%Post{title: "1", temp: "special"}) assert TestRepo.get(Post, id).temp == "temp" end + test "virtual embed" do + assert %Order{id: id, last_seen_item: %Item{reference: "1"}} = + TestRepo.insert!(%Order{last_seen_item: %Item{reference: "1"}}) + + assert %{last_seen_item: nil} = TestRepo.get(Order, id) + end + ## Query syntax defmodule Foo do diff --git a/integration_test/support/schemas.exs b/integration_test/support/schemas.exs index 5dbb78f707..ce5aa98058 100644 --- a/integration_test/support/schemas.exs +++ b/integration_test/support/schemas.exs @@ -275,6 +275,7 @@ defmodule Ecto.Integration.Order do * Embedding one schema * Preloading items inside embeds_many * Preloading items inside embeds_one + * Virtual embeds * Field source with json_extract_path """ @@ -282,6 +283,7 @@ defmodule Ecto.Integration.Order do schema "orders" do field :metadata, :map, source: :meta + embeds_one :last_seen_item, Ecto.Integration.Item, virtual: true embeds_one :item, Ecto.Integration.Item embeds_many :items, Ecto.Integration.Item belongs_to :permalink, Ecto.Integration.Permalink diff --git a/lib/ecto/embedded.ex b/lib/ecto/embedded.ex index e8c5fb28fb..bd9ac210ea 100644 --- a/lib/ecto/embedded.ex +++ b/lib/ecto/embedded.ex @@ -28,6 +28,7 @@ defmodule Ecto.Embedded do :owner, :related, :on_cast, + virtual: false, on_replace: :raise, unique: true, ordered: true @@ -149,7 +150,9 @@ defmodule Ecto.Embedded do @doc false def prepare(changeset, embeds, adapter, repo_action) do %{changes: changes, types: types, repo: repo} = changeset - prepare(Map.take(changes, embeds), types, adapter, repo, repo_action) + changes + |> Map.take(embeds) + |> prepare(types, adapter, repo, repo_action) end defp prepare(embeds, _types, _adapter, _repo, _repo_action) when embeds == %{} do @@ -158,8 +161,14 @@ defmodule Ecto.Embedded do defp prepare(embeds, types, adapter, repo, repo_action) do Enum.reduce(embeds, embeds, fn {name, changeset_or_changesets}, acc -> - {:embed, embed} = Map.get(types, name) - Map.put(acc, name, prepare_each(embed, changeset_or_changesets, adapter, repo, repo_action)) + case Map.get(types, name) do + {:embed, %{virtual: false} = embed} -> + prepared = prepare_each(embed, changeset_or_changesets, adapter, repo, repo_action) + Map.put(acc, name, prepared) + + {:embed, %{virtual: true}} -> + acc + end end) end diff --git a/lib/ecto/schema.ex b/lib/ecto/schema.ex index 1c723622bf..88b3e77176 100644 --- a/lib/ecto/schema.ex +++ b/lib/ecto/schema.ex @@ -2143,7 +2143,7 @@ defmodule Ecto.Schema do Module.put_attribute(mod, :ecto_changeset_fields, {name, {:assoc, struct}}) end - @valid_embeds_one_options [:on_replace, :source, :load_in_query, :defaults_to_struct] + @valid_embeds_one_options [:on_replace, :source, :load_in_query, :defaults_to_struct, :virtual] @doc false def __embeds_one__(mod, name, schema, opts) when is_atom(schema) do @@ -2164,7 +2164,7 @@ defmodule Ecto.Schema do "`embeds_one/3` expects `schema` to be a module name, but received #{inspect(schema)}" end - @valid_embeds_many_options [:on_replace, :source, :load_in_query] + @valid_embeds_many_options [:on_replace, :source, :load_in_query, :virtual] @doc false def __embeds_many__(mod, name, schema, opts) when is_atom(schema) do diff --git a/test/ecto/embedded_test.exs b/test/ecto/embedded_test.exs index 162107b6c2..4ad1d1cb98 100644 --- a/test/ecto/embedded_test.exs +++ b/test/ecto/embedded_test.exs @@ -19,6 +19,7 @@ defmodule Ecto.EmbeddedTest do field :name, :string embeds_one :profile, Profile, on_replace: :delete embeds_one :post, Post + embeds_one :virtual_post, Post, virtual: true embeds_many :posts, Post, on_replace: :delete end end @@ -45,7 +46,7 @@ defmodule Ecto.EmbeddedTest do test "__schema__" do assert Author.__schema__(:embeds) == - [:profile, :post, :posts] + [:profile, :post, :virtual_post, :posts] assert Author.__schema__(:embed, :profile) == %Embedded{field: :profile, cardinality: :one, owner: Author, on_replace: :delete, related: Profile} diff --git a/test/ecto/query/planner_test.exs b/test/ecto/query/planner_test.exs index 2eca569430..10e3f3a696 100644 --- a/test/ecto/query/planner_test.exs +++ b/test/ecto/query/planner_test.exs @@ -601,7 +601,8 @@ defmodule Ecto.Query.PlannerTest do test "plan: generates a cache key" do {_query, _cast_params, _dump_params, key} = plan(from(Post, [])) - assert key == [:all, {:from, {"posts", Post, 50_009_106, "my_prefix"}, []}] + + assert key == [:all, {:from, {"posts", Post, 132715331, "my_prefix"}, []}] query = from( @@ -633,9 +634,18 @@ defmodule Ecto.Query.PlannerTest do [ {:inner, {"comments", Comment, 38_292_156, "world"}, true, ["join hint"]} ]}, - {:from, {"posts", Post, 50_009_106, "hello"}, ["hint"]}, + {:from, {"posts", Post, 132715331, "hello"}, ["hint"]}, {:select, 1} ] + + assert key == [:all, + {:lock, "foo"}, + {:prefix, "foo"}, + {:limit, {true, 1}}, + {:where, [{:and, {:is_nil, [], [nil]}}, {:or, {:is_nil, [], [nil]}}]}, + {:join, [{:inner, {"comments", Comment, 38292156, "world"}, true, ["join hint"]}]}, + {:from, {"posts", Post, 132715331, "hello"}, ["hint"]}, + {:select, 1}] end test "plan: generates a cache key for in based on the adapter" do @@ -955,7 +965,7 @@ defmodule Ecto.Query.PlannerTest do [ :all, {:aliases, %{post: 0}}, - {:from, {"posts", Ecto.Query.PlannerTest.Post, 50_009_106, "my_prefix"}, []}, + {:from, {"posts", Ecto.Query.PlannerTest.Post, 132715331, "my_prefix"}, []}, {:select, {{:%{}, [], [