Skip to content

Latest commit

 

History

History
211 lines (143 loc) · 5.69 KB

backwards-compatibility-config.md

File metadata and controls

211 lines (143 loc) · 5.69 KB

Backwards Compatibility Config

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.

allow_forbidden_field_for_relationships_by_default?

config :ash, allow_forbidden_field_for_relationships_by_default?: true

Old Behavior

Loaded 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.

New Behavior

Now the relationships that produced a forbidden error are instead populated with %Ash.ForbiddenField{}.

include_embedded_source_by_default?

config :ash, include_embedded_source_by_default?: false

Old Behavior

When 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.

New Behavior

Now, the source is only included when you say constraints: [include_source?: true] on the embedded resource's usage.

show_keysets_for_all_actions?

config :ash, show_keysets_for_all_actions?: false

Old Behavior

For 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.

New Behavior

Only when actually performing keyset pagination will the __metadata__.keyset be computed.

default_page_type

config :ash, default_page_type: :keyset

Old Behavior

When an action supports offset and keyset pagination, and a page is requested with only limit set, i.e page: [limit: 10], you would get back an %Ash.Page.Offset{}.

New Behavior

Now we will return a %Ash.Page.Keyset{} choosing it whenever it is ambiguous. You can always force returning an %Ash.Page.Offset{} by providing the offset option, i.e page: [offset: 0]

policies.no_filter_static_forbidden_reads?

config :ash, policies: [no_filter_static_forbidden_reads?: false]

Old Behavior

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)
end

In 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)
end

You 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.

New Behavior

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)
end

would 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.

keep_read_action_loads_when_loading?

config :ash, keep_read_action_loads_when_loading?: false

Old Behavior

If 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.

New Behavior

When using Ash.load only the explicitly provided load statement is applied.

default_actions_require_atomic?

config :ash, default_actions_require_atomic?: true

Old Behavior

When 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.

New Behavior

The default generated actions are generated with require_atomic? true

read_action_after_action_hooks_in_order?

config :ash, read_action_after_action_hooks_in_order?: true

Old Behavior

In 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
end

running that action would print hook 2 before hook 1.

New Behavior

Read action hooks are now run in the order they were added

bulk_actions_default_to_errors?

config :ash, bulk_actions_default_to_errors?: true

Old Behavior

Bulk 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?"

New Behavior

Now, return_errors? and stop_on_error? default to true