Skip to content

Commit 0d2a56e

Browse files
Remove read-only changes from returned record during insert/update (#4525)
1 parent a1a5047 commit 0d2a56e

File tree

4 files changed

+36
-15
lines changed

4 files changed

+36
-15
lines changed

lib/ecto/repo/schema.ex

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ defmodule Ecto.Repo.Schema do
362362
struct = struct_from_changeset!(:insert, changeset)
363363
schema = struct.__struct__
364364
dumper = schema.__schema__(:dump)
365-
insertable_fields = schema.__schema__(:insertable_fields)
365+
{keep_fields, drop_fields} = schema.__schema__(:insertable_fields)
366366
assocs = schema.__schema__(:associations)
367367
embeds = schema.__schema__(:embeds)
368368

@@ -379,7 +379,8 @@ defmodule Ecto.Repo.Schema do
379379
# On insert, we always merge the whole struct into the
380380
# changeset as changes, except the primary key if it is nil.
381381
changeset = put_repo_and_action(changeset, :insert, repo, tuplet)
382-
changeset = Relation.surface_changes(changeset, struct, insertable_fields ++ assocs)
382+
changeset = Relation.surface_changes(changeset, struct, keep_fields ++ assocs)
383+
changeset = update_in(changeset.changes, &Map.drop(&1, drop_fields))
383384

384385
wrap_in_transaction(adapter, adapter_meta, opts, changeset, assocs, embeds, prepare, fn ->
385386
assoc_opts = assoc_opts(assocs, opts)
@@ -398,7 +399,7 @@ defmodule Ecto.Repo.Schema do
398399
{changes, cast_extra, dump_extra, return_types, return_sources} =
399400
autogenerate_id(autogen_id, changes, return_types, return_sources, adapter)
400401

401-
changes = Map.take(changes, insertable_fields)
402+
changes = Map.take(changes, keep_fields)
402403
autogen = autogenerate_changes(schema, :insert, changes)
403404

404405
dump_changes =
@@ -454,7 +455,7 @@ defmodule Ecto.Repo.Schema do
454455
struct = struct_from_changeset!(:update, changeset)
455456
schema = struct.__struct__
456457
dumper = schema.__schema__(:dump)
457-
updatable_fields = schema.__schema__(:updatable_fields)
458+
{keep_fields, drop_fields} = schema.__schema__(:updatable_fields)
458459
assocs = schema.__schema__(:associations)
459460
embeds = schema.__schema__(:embeds)
460461

@@ -471,6 +472,7 @@ defmodule Ecto.Repo.Schema do
471472
# fields into the changeset. All changes must be in the
472473
# changeset before hand.
473474
changeset = put_repo_and_action(changeset, :update, repo, tuplet)
475+
changeset = update_in(changeset.changes, &Map.drop(&1, drop_fields))
474476

475477
if changeset.changes != %{} or force? do
476478
wrap_in_transaction(adapter, adapter_meta, opts, changeset, assocs, embeds, prepare, fn ->
@@ -483,7 +485,7 @@ defmodule Ecto.Repo.Schema do
483485
if changeset.valid? do
484486
embeds = Ecto.Embedded.prepare(changeset, embeds, adapter, :update)
485487

486-
changes = changeset.changes |> Map.merge(embeds) |> Map.take(updatable_fields)
488+
changes = changeset.changes |> Map.merge(embeds) |> Map.take(keep_fields)
487489
autogen = autogenerate_changes(schema, :update, changes)
488490
dump_changes = dump_changes!(:update, changes, autogen, schema, [], dumper, adapter)
489491

@@ -797,7 +799,8 @@ defmodule Ecto.Repo.Schema do
797799
end
798800

799801
defp replace_all_fields!(_kind, schema, to_remove) do
800-
Enum.map(schema.__schema__(:updatable_fields) -- to_remove, &field_source!(schema, &1))
802+
{updatable_fields, _} = schema.__schema__(:updatable_fields)
803+
Enum.map(updatable_fields -- to_remove, &field_source!(schema, &1))
801804
end
802805

803806
defp field_source!(nil, field) do

lib/ecto/schema.ex

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -671,11 +671,29 @@ defmodule Ecto.Schema do
671671
def __schema__(:redact_fields), do: unquote(redacted_fields)
672672
def __schema__(:virtual_fields), do: unquote(Enum.map(virtual_fields, &elem(&1, 0)))
673673

674-
def __schema__(:updatable_fields),
675-
do: unquote(for {name, {_, :always}} <- fields, do: name)
674+
def __schema__(:updatable_fields) do
675+
unquote(
676+
for {name, {_, writable}} <- fields, reduce: {[], []} do
677+
{keep, drop} ->
678+
case writable do
679+
:always -> {[name | keep], drop}
680+
_ -> {keep, [name | drop]}
681+
end
682+
end
683+
)
684+
end
676685

677-
def __schema__(:insertable_fields),
678-
do: unquote(for {name, {_, writable}} when writable != :never <- fields, do: name)
686+
def __schema__(:insertable_fields) do
687+
unquote(
688+
for {name, {_, writable}} <- fields, reduce: {[], []} do
689+
{keep, drop} ->
690+
case writable do
691+
:never -> {keep, [name | drop]}
692+
_ -> {[name | keep], drop}
693+
end
694+
end
695+
)
696+
end
679697

680698
def __schema__(:autogenerate_fields),
681699
do: unquote(Enum.flat_map(autogenerate, &elem(&1, 0)))

test/ecto/repo_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,13 +1839,13 @@ defmodule Ecto.RepoTest do
18391839

18401840
describe "on conflict" do
18411841
test "passes all fields on replace_all" do
1842-
fields = [:id, :x, :yyy, :z, :array, :map]
1842+
fields = [:map, :array, :z, :yyy, :x, :id]
18431843
TestRepo.insert(%MySchema{id: 1}, on_conflict: :replace_all)
18441844
assert_received {:insert, %{source: "my_schema", on_conflict: {^fields, [], []}}}
18451845
end
18461846

18471847
test "passes all fields+embeds on replace_all" do
1848-
fields = [:id, :x, :embeds]
1848+
fields = [:embeds, :x, :id]
18491849
TestRepo.insert(%MySchemaEmbedsMany{id: 1}, on_conflict: :replace_all)
18501850
assert_received {:insert, %{source: "my_schema", on_conflict: {^fields, [], []}}}
18511851
end
@@ -1875,7 +1875,7 @@ defmodule Ecto.RepoTest do
18751875
end
18761876

18771877
test "passes all fields except given fields" do
1878-
fields = [:x, :yyy, :z, :map]
1878+
fields = [:map, :z, :yyy, :x]
18791879
TestRepo.insert(%MySchema{id: 1}, on_conflict: {:replace_all_except, [:id, :array]})
18801880
assert_received {:insert, %{source: "my_schema", on_conflict: {^fields, [], []}}}
18811881
end

test/ecto/schema_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ defmodule Ecto.SchemaTest do
3030
[:id, :name, :email, :password, :count, :array, :uuid, :no_query_load, :unwritable, :non_updatable, :comment_id]
3131

3232
assert Schema.__schema__(:insertable_fields) ==
33-
[:id, :name, :email, :password, :count, :array, :uuid, :no_query_load, :non_updatable, :comment_id]
33+
{[:comment_id, :non_updatable, :no_query_load, :uuid, :array, :count, :password, :email, :name, :id], [:unwritable]}
3434

3535
assert Schema.__schema__(:updatable_fields) ==
36-
[:id, :name, :email, :password, :count, :array, :uuid, :no_query_load, :comment_id]
36+
{[:comment_id, :no_query_load, :uuid, :array, :count, :password, :email, :name, :id], [:non_updatable, :unwritable]}
3737

3838
assert Schema.__schema__(:virtual_fields) == [:temp]
3939

0 commit comments

Comments
 (0)