diff --git a/lib/ecto/changeset.ex b/lib/ecto/changeset.ex index cd0daeffe9..a5051b0838 100644 --- a/lib/ecto/changeset.ex +++ b/lib/ecto/changeset.ex @@ -2190,6 +2190,9 @@ defmodule Ecto.Changeset do regardless if the changeset is valid or not. See `apply_action/2` for a similar function that ensures the changeset is valid. + If a field of type `:map` contains a schemaless changeset, + its changes too will be applied and returned as a map with atom keys. + ## Examples iex> changeset = change(%Post{author: "bar"}, %{title: "foo"}) @@ -2208,6 +2211,9 @@ defmodule Ecto.Changeset do {:ok, {tag, relation}} when tag in @relations -> apply_relation_changes(acc, key, relation, value) + {:ok, :map} when is_struct(value, Changeset) -> + Map.put(acc, key, apply_changes(value)) + {:ok, _} -> Map.put(acc, key, value) diff --git a/test/ecto/changeset_test.exs b/test/ecto/changeset_test.exs index 69e4400ee7..41e50c2d86 100644 --- a/test/ecto/changeset_test.exs +++ b/test/ecto/changeset_test.exs @@ -1073,6 +1073,23 @@ defmodule Ecto.ChangesetTest do assert changed_post.category == nil end + test "apply_changes/1 with nested schemaless validation" do + params = %{"seo_metadata" => %{"keywords" => ["foo", "bar"], "slug" => "my-post-1"}} + + changeset = + %Post{} + |> changeset(params) + |> update_change(:seo_metadata, fn seo_metadata -> + {%{}, %{keywords: {:array, :string}, slug: :string}} + |> cast(seo_metadata || %{}, [:keywords, :slug]) + |> validate_required([:keywords, :slug]) + end) + + changed_post = apply_changes(changeset) + + assert changed_post.seo_metadata == %{keywords: ["foo", "bar"], slug: "my-post-1"} + end + describe "apply_action/2" do test "valid changeset" do post = %Post{}