All of these configurations are potentially breaking changes when applied to your application. However, we highly encourage setting as many of them as possible. In 4.0, some will be removed entirely, and any that remain will have their defaults changed to the new value.
The ash installer automatically sets all of these.
config :ash, allow_forbidden_field_for_relationships_by_default?: trueLoaded relationships that produced a Forbidden error would fail the entire
request. i.e in Ash.load(post, [:comments, :author]), if author returned
a Forbidden error, the entire request would fail with a forbidden error.
Now the relationships that produced a forbidden error are instead populated
with %Ash.ForbiddenField{}.
config :ash, include_embedded_source_by_default?: falseWhen working with embedded types, the __source__ constraint is populated with
the original changeset. This can be very costly in terms of memory when working with
large sets of embedded resources.
Now, the source is only included when you say constraints: [include_source?: true] on
the embedded resource's usage.
config :ash, show_keysets_for_all_actions?: falseFor all actions, the records would be returned with __metadata__.keyset populated
with a keyset computed for the sort that was used to produce those records. This
is expensive as it requires loading all things that are used by the sort.
Only when actually performing keyset pagination will the __metadata__.keyset be
computed.
config :ash, default_page_type: :keysetWhen an action supports both offset and keyset pagination, and a page is requested
with only limit set (i.e., page: [limit: 10]), Ash defaulted to offset pagination
and returned an %Ash.Page.Offset{}.
With the current default configuration, Ash will now return an %Ash.Page.Keyset{} when the pagination
type is ambiguous (only limit is provided).
For detailed pagination behavior documentation, see the pagination guide.
config :ash, policies: [no_filter_static_forbidden_reads?: false]On read action policies, we can often tell statically that they cannot pass, for example:
policy action_type(:read) do
authorize_if actor_attribute_equals(:active, true)
endIn these cases, you would get an Ash.Error.Forbidden, despite the fact that the
default access_type for a policy is :filter. If you instead had:
policy action_type(:read) do
authorize_if expr(private == false)
endYou would get a filter. This made it difficult to predict when you would get a forbidden error and when the query results would be filtered.
Now, we always filter the query even if we know statically that the request would be forbidden. For example the following policy:
policy action_type(:read) do
authorize_if actor_attribute_equals(:active, true)
endwould yield filter: false. This makes the behavior consistent and predictable.
You can always annotate that a given policy should result in a forbidden error
by setting access_type :strict in the policy.
config :ash, keep_read_action_loads_when_loading?: falseIf you had an action with a preparation, or a global preparation that loaded data, i.e
prepare build(load: :comments)this wold be applied when using Ash.load, because we build a query for the primary
read action as a basis for loading data. This could be expensive because now you are always
loading :comments even if you only intended to load something else, and could also be
unpredictable because it could "overwrite" the already loaded comments on the data you
passed in.
When using Ash.load only the explicitly provided load statement is applied.
config :ash, default_actions_require_atomic?: trueWhen building actions like so: defaults [:read, create: :*, update: :*] the default
action is generated with require_atomic? false. This could make it difficult to spot
actions that cannot safely be done asynchronously.
The default generated actions are generated with require_atomic? true
config :ash, read_action_after_action_hooks_in_order?: trueIn 3.0, we modified hooks on changesets to always be added in order instead of in
reverse order. This was missed for Ash.Query. Meaning if you had something like this:
read :read do
prepare fn query, _ ->
Ash.Query.after_action(query, fn query, results ->
IO.puts("hook 1")
{:ok, results}
end)
end
prepare fn query, _ ->
Ash.Query.after_action(query, fn query, results ->
IO.puts("hook 2")
{:ok, results}
end)
end
endrunning that action would print hook 2 before hook 1.
Read action hooks are now run in the order they were added
config :ash, bulk_actions_default_to_errors?: trueBulk action options defaulted to return_errors?: false, and stop_on_error?: false,
which was often a footgun for users unfamiliar to bulk actions, wondering "why did I not
get an error even though nothing was created?"
Now, return_errors? and stop_on_error? default to true
config :ash, Ash.Type.UUIDv7, match_v4_uuids?: trueIncorrectly allowed non UUIDv7's to be loaded.
Configuring the variable allows UUIDv4's to be loaded.
config :ash, redact_sensitive_values_in_errors?: trueBuilt-in validations would include the actual field value in error structs even when
the field was marked sensitive?: true. This could leak sensitive data (e.g. passwords,
tokens) through error messages.
When a field is marked sensitive?: true, its value in validation error structs is
replaced with a redacted placeholder. This applies to both
the non-atomic (validate/3) and atomic (atomic/3) code paths across all built-in
validations.