diff --git a/lib/ecto/schema.ex b/lib/ecto/schema.ex index c8768f1471..6fd48378a3 100644 --- a/lib/ecto/schema.ex +++ b/lib/ecto/schema.ex @@ -130,6 +130,9 @@ defmodule Ecto.Schema do inspect on the schema unless the schema module is tagged with the option `@derive_inspect_for_redacted_fields false`. + A schema module tagged with `@schema_redact :all_except_primary_keys` will + redact all fields except primary keys. + ## Schema attributes Supported attributes for configuring the defined schema. They must @@ -155,6 +158,11 @@ defmodule Ecto.Schema do which generates structs and queries without context. Context are not used by the built-in SQL adapters. + * `@schema_redact` - If set to `:all_except_primary_keys`, Ecto will + treat all non-primary key fields as if they were individually marked + as redacted. Defaults to `false`, as no fields are redacted by default. + The value set here can be changed per field through the `:redact` option. + * `@foreign_key_type` - configures the default foreign key type used by `belongs_to` associations. It must be set in the same module that defines the `belongs_to`. Defaults to `:id`; @@ -1992,7 +2000,14 @@ defmodule Ecto.Schema do writable = opts[:writable] || :always put_struct_field(mod, name, Keyword.get(opts, :default)) - if Keyword.get(opts, :redact, false) do + redact_field? = Keyword.get_lazy(opts, :redact, fn -> + case Module.get_attribute(mod, :schema_redact, false) do + :all_except_primary_keys -> not pk? + false -> false + end + end) + + if redact_field? do Module.put_attribute(mod, :ecto_redact_fields, name) end diff --git a/test/ecto/changeset_test.exs b/test/ecto/changeset_test.exs index ede164927d..b70216f04f 100644 --- a/test/ecto/changeset_test.exs +++ b/test/ecto/changeset_test.exs @@ -3607,6 +3607,17 @@ defmodule Ecto.ChangesetTest do end end + defmodule RedactAllExceptPrimaryKeysSchema do + use Ecto.Schema + + @schema_redact :all_except_primary_keys + schema "redacted_schema" do + field :password, :string + field :username, :string + field :virtual_pass, :string, virtual: true + end + end + defmodule RedactedEmbeddedSchema do use Ecto.Schema @@ -3683,5 +3694,12 @@ defmodule Ecto.ChangesetTest do assert inspect(changeset) =~ "hunter2" refute inspect(changeset) =~ "**redacted**" end + + test "redacts all non-primary-key fields when schema sets @schema_redact :all_except_primary_keys" do + changeset = Ecto.Changeset.cast(%RedactAllExceptPrimaryKeysSchema{}, %{username: "Hunter", password: "hunter2"}, [:username, :password]) + assert inspect(changeset) =~ "id" + refute inspect(changeset) =~ "hunter2" + assert inspect(changeset) =~ "**redacted**" + end end end diff --git a/test/ecto/schema_test.exs b/test/ecto/schema_test.exs index 15e9c27b96..d84d78a78e 100644 --- a/test/ecto/schema_test.exs +++ b/test/ecto/schema_test.exs @@ -192,6 +192,37 @@ defmodule Ecto.SchemaTest do refute inspect(%Schema{temp: "hunter2"}) =~ "hunter2" end + defmodule SchemaWithRedactAllExceptPrimaryKeys do + use Ecto.Schema + + @schema_redact :all_except_primary_keys + schema "my_schema" do + field :password, :string + field :temp, :any, default: "temp", virtual: true + end + end + + test "schema with @schema_redact: :all_except_primary_keys derives inspect for all non-primary-key fields" do + inspected_schema = inspect(%SchemaWithRedactAllExceptPrimaryKeys{password: "hunter2"}) + + assert inspected_schema =~ "id" + refute inspected_schema =~ "hunter2" + refute inspected_schema =~ "temp" + end + + defmodule SchemaWithRedactFalse do + use Ecto.Schema + + @schema_redact false + schema "my_schema" do + field :name, :string + end + end + + test "schema with @schema_redact: false doesn't derive inspect" do + assert inspect(%SchemaWithRedactFalse{name: "Hunter"}) =~ "Hunter" + end + defmodule SchemaWithoutDeriveInspect do use Ecto.Schema