diff --git a/lib/ecto/repo/schema.ex b/lib/ecto/repo/schema.ex index 1b87e748e8..784665acb1 100644 --- a/lib/ecto/repo/schema.ex +++ b/lib/ecto/repo/schema.ex @@ -665,7 +665,15 @@ defmodule Ecto.Repo.Schema do defp handle_writable_violation(field, schema, action) do on_writable_violation = schema.__schema__(:on_writable_violation, field) - message = "attempted to write to non-writable field #{inspect(field)} during #{action}" + message = """ + you are attempting to write to the field #{inspect(field)} of #{inspect(schema)} but + the `:writable` option of this field indicates the field should not be written to during an #{action}. + + If you want to write to this field, please set the appropriate `:writable` option when defining the field. + + If you want to customize the behavior of writing to a non-writable field, + please set the appropriate `:on_writable_violation` option when defining the field. + """ case on_writable_violation do :raise -> diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs index cd90759701..7a2141d584 100644 --- a/test/ecto/repo_test.exs +++ b/test/ecto/repo_test.exs @@ -2458,18 +2458,35 @@ defmodule Ecto.RepoTest do assert_received {:update, %{changes: [always: 10]}} end) - assert log =~ "attempted to write to non-writable field :insert during update" - assert log =~ "attempted to write to non-writable field :never during update" + assert log =~ ~r""" + you are attempting to write to the field :insert of #{inspect(__MODULE__.MySchemaWritableWarn)} but + the `:writable` option of this field indicates the field should not be written to during an update. + """ + + assert log =~ ~r""" + you are attempting to write to the field :never of #{inspect(__MODULE__.MySchemaWritableWarn)} but + the `:writable` option of this field indicates the field should not be written to during an update. + """ end test "update with on_writable_violation: :raise saves changes for writable: :always and raises for changes for writable: :insert/:never" do - assert_raise ArgumentError, "attempted to write to non-writable field :never during update", fn -> + never_message = ~r""" + you are attempting to write to the field :never of #{inspect(__MODULE__.MySchemaWritableRaise)} but + the `:writable` option of this field indicates the field should not be written to during an update. + """ + + assert_raise ArgumentError, never_message, fn -> %MySchemaWritableRaise{id: 1} |> Ecto.Changeset.change(%{never: 10}) |> TestRepo.update!() end - assert_raise ArgumentError, "attempted to write to non-writable field :insert during update", fn -> + insert_message = ~r""" + you are attempting to write to the field :insert of #{inspect(__MODULE__.MySchemaWritableRaise)} but + the `:writable` option of this field indicates the field should not be written to during an update. + """ + + assert_raise ArgumentError, insert_message, fn -> %MySchemaWritableRaise{id: 2} |> Ecto.Changeset.change(%{insert: 11}) |> TestRepo.update!() @@ -2552,7 +2569,10 @@ defmodule Ecto.RepoTest do assert Enum.sort(inserted_fields) == [always: 10, id: 1, insert: 12] end) - assert log =~ "attempted to write to non-writable field :never during insert" + assert log =~ ~r""" + you are attempting to write to the field :never of #{inspect(__MODULE__.MySchemaWritableWarn)} but + the `:writable` option of this field indicates the field should not be written to during an insert. + """ end test "insert with on_writable_violation: :warn saves changes for writable: :always/:insert, ignores changes for writable: :never, and logs a warning" do @@ -2566,17 +2586,24 @@ defmodule Ecto.RepoTest do assert Enum.sort(inserted_fields) == [always: 10, id: 1, insert: 12] end) - assert log =~ "attempted to write to non-writable field :never during insert" + assert log =~ ~r""" + you are attempting to write to the field :never of #{inspect(__MODULE__.MySchemaWritableWarn)} but + the `:writable` option of this field indicates the field should not be written to during an insert. + """ end test "insert with surfaced changes and on_writable_violation: :raise saves changes for writable: :always/:insert and raises for changes for writable: :never" do - assert_raise ArgumentError, "attempted to write to non-writable field :never during insert", fn -> + message = ~r""" + you are attempting to write to the field :never of #{inspect(__MODULE__.MySchemaWritableRaise)} but + the `:writable` option of this field indicates the field should not be written to during an insert. + """ + + assert_raise ArgumentError, message, fn -> %MySchemaWritableRaise{id: 1, never: 10} |> Ecto.Changeset.change(%{}) |> TestRepo.insert!() end - %MySchemaWritableRaise{id: 2, insert: 11, always: 12} |> Ecto.Changeset.change(%{}) |> TestRepo.insert!() @@ -2586,7 +2613,12 @@ defmodule Ecto.RepoTest do end test "insert with on_writable_violation: :raise saves changes for writable: :always/:insert and raises for changes for writable: :never" do - assert_raise ArgumentError, "attempted to write to non-writable field :never during insert", fn -> + message = ~r""" + you are attempting to write to the field :never of #{inspect(__MODULE__.MySchemaWritableRaise)} but + the `:writable` option of this field indicates the field should not be written to during an insert. + """ + + assert_raise ArgumentError, message, fn -> %MySchemaWritableRaise{id: 1} |> Ecto.Changeset.change(%{never: 10}) |> TestRepo.insert!()