mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 21:43:02 +12:00
5967ed3a48
* improvement!: use `%Ash.NotSelected{}` for unselected values * improvement!: default `require_atomic?` to `true` * improvement!: raise errors on unknown generic action arguments * improvement!: default bulk strategy to `:atomic` * improvement!: warnings on `require_atomic?` `true` actions improvement!: revise `Ash.NotSelected` to `Ash.NotLoaded` improvement!: errors on unknown action inputs across the board * doc: clarify wording in notifiers.md closes #889 * improvement!: default `api.authorization.authorize` to `:by_default` * improvement!: require the api when constructing changesets this commit also fixes some work from prior commits around the default value for the `authorize` option * improvement!: code_interface.define_for -> code_interface.api `code_interface.define_for` is now `code_interface.api`. Additionally, it is set automatically if the `api` option is specified on `use Ash.Resource`. * improvement!: remove registries * improvement!: pubsub notifier default to `previous_values?: false` improvement!: requires_original_data? callback defaults to false * improvement!: rename Ash.Calculation -> Ash.Resource.Calculation improvement!: improve `Ash.Query.Calculation.new` signature improvement!: anonymous function calculations now take lists and return lists improvement!: make callback contexts into structs improvement!: pass context to builtin lifecycle hook changes improvement!: calculation arguments are now in the `arguments` key of the context * chore: fix build * improvement!: remove `aggregates` and `calculations` from `Filter.parse` and `Filter.parse_input` * improvement: update spark to 2.0 * improvement!: make picosat_elixir optional with `simple_sat` * improvement!: rename api to domain * docs: add more info to upgrading guide * docs: tweak docs formatting * improvement!: remove `Ash.Changeset.new!` * docs: update docs for `Ash.Changeset.new/1` * improvement!: deprecate `private?: false` in favor of `public?: true` * doc: add upgrade guide for private -> public * improvement: update reactor to 3.0 * improvement!: default `default_accept` is now `[]` * improvement!: `Ash.CiString.new/1` returns `nil` on `nil` input * improvement!(Ash.Reactor): Improve integration with Ash 3.0 changes. * improvement!: clean up and reorganize `Ash` functions this is in preparation of deprecating the functions that are defined on the api improvement!: remove context-based functionality * chore: update docs references from `Ash.Domain` to `Ash` * chore: fix bad merge * chore: fix context access in atomic changes * improvement!: Deprecate calling functions on (domain) api in favor of `Ash` * improvement!: add `attribute_public?` and update `attribute_writable?` behavior * improvement!: update atomic behaviors, default to invalid * chore: update downcase docs * improvement!: changeset.filters -> changeset.filter * improvement!: remove deprecated functions * improvement!: remove and simplify `Ash.Filter.TemplateHelpers` * improvement: import Ash.Expr in modules where it is used improvement: require Ash.QUery in modules where it makes sense * fix!: keyword lists are no longer special cased in ash expressions * improvement: add structs for more context implementations * chore: small tweaks, finish `:all` -> `:*` conversion * chore: update DSL docs for multitenancy.global? * improvement: ensure selects are applied on destroys chore: remove TODOs * chore: some docs changes * improvement!: introduce strict mode to calculations * chore: update tests * improvement: support custom expressions * docs: document custom expressions * chore: fix and test custom expressions and function fragments docs: update relevant docs w/ the changes * improvement!: reverse order of before action & before transaction hooks * improvement!: default read actions are now paginatable * improvement!: require explicit accept lists in default actions * chore: update docs * improvement!: remove Ash.Flow and Ash.Engine * chore: unlock unused deps * chore: don't use unused variable * chore: include ash flow change in upgrade guide * improvement!: standardize various exception keys and names * improvement!: use `Splode` for errors * improvement: update upgrade guide to include Splode * feat: code interface on the domain * improvement: only require primary key if resource has actions or fields improvement: only build schema if resource has actions or fields improvement: verify primary key in its own verifier * improvement: add `resource/1` builtin check * improvement!: move simple_notifiers to an option instead of a DSL builder improvement!: update spark for better autocomplete, configure autocomplete for key functions docs: replace `an domain` with `a domain` * improvement: better code interface documentation * fix: set tenant on query so that root calles to Api.aggreagte work as expected (#929) * chore: fixes from previous improvements * chore: update splode * chore: update splode * improvement!: swap position of sort order and arguments in calculation sorting * improvement!: add `include_nil?` aggregate option, and default it to `false` * improvement: support notifiers within actions * improvement: support specifying multiple filters * improvement: add `sortable?` flags to all fields improvement: support multiple filters on relationships * improvement: support sensitive? on calculations and arguments * improvement: validate resources in inputs to code interface * chore: don't require explicit accept lists when using `default_accept :*` * chore: update spark * chore: update public attribute handling per 3.0 * improvement: update reactor and tests * chore: better error message * chore: fix rebase issue * chore: handle merge issues improvement: don't require domain on relationships if destination has domain * improvement!: errors on unknown inputs for calculations * improvement: always choose to cast atomic * improvement: support casting some embeds atomically * improvement: various 3.0 updates, documented in upgrade.md * chore: Add failing tests for loads with with explicit domains. (#948) Co-authored-by: James Harton <james@harton.nz> * improvement: ensure non-static dynamic domains works * improvement: add Ash.ToTenant protocol * chore: add docs for no ToTenant option * fix: properly construct new query in `build/3` * chore: update simple_sat dependency * chore: don't reselect when missing primary keys * chore: remove IO.inspect * chore: update spark * chore: update spark * improvement: use `Keyword.put_new` in `Ash.Context.to_opts` (#953) * improvement: support bulk and atomic operations in code interfaces --------- Co-authored-by: James Harton <james@harton.nz> Co-authored-by: WIGGLES <55168935+WIGGLES-dev@users.noreply.github.com> Co-authored-by: Dmitry Maganov <vonagam@gmail.com>
170 lines
5.5 KiB
Markdown
170 lines
5.5 KiB
Markdown
# Reactor
|
|
|
|
`Ash.Reactor` is an extension for [`Reactor`](https://github.com/ash-project/reactor) which adds explicit support for interacting with resources via their defined actions.
|
|
|
|
See [Getting started with Reactor](https://hexdocs.pm/reactor/getting-started-with-reactor.html) for more information about Reactor.
|
|
|
|
## Usage
|
|
|
|
You can either add the `Ash.Reactor` extension to your existing reactors eg:
|
|
|
|
```elixir
|
|
defmodule MyExistingReactor do
|
|
use Reactor, extensions: [Ash.Reactor]
|
|
end
|
|
```
|
|
|
|
or for your convenience you can use `use Ash.Reactor` which expands to exactly the same as above.
|
|
|
|
## Example
|
|
|
|
An example is worth 1000 words of prose:
|
|
|
|
```elixir
|
|
defmodule ExampleReactor do
|
|
use Ash.Reactor
|
|
|
|
ash do
|
|
default_domain ExampleDomain
|
|
end
|
|
|
|
input :customer_name
|
|
input :customer_email
|
|
input :plan_name
|
|
input :payment_nonce
|
|
|
|
create :create_customer, Customer do
|
|
inputs %{name: input(:customer_name), email: input(:customer_email)}
|
|
end
|
|
|
|
read_one :get_plan, Plan, :get_plan_by_name do
|
|
inputs %{name: input(:plan_name)}
|
|
fail_on_not_found? true
|
|
end
|
|
|
|
action :take_payment, PaymentProvider do
|
|
inputs %{
|
|
nonce: input(:payment_nonce),
|
|
amount: result(:get_plan, [:price])
|
|
}
|
|
end
|
|
|
|
create :subscription, Subscription do
|
|
inputs %{
|
|
plan_id: result(:get_plan, [:id]),
|
|
payment_provider_id: result(:take_payment, :id)
|
|
}
|
|
end
|
|
end
|
|
```
|
|
|
|
## Actions
|
|
|
|
For each action type there is a corresponding step DSL, which needs a name (used
|
|
to refer to the result of the step by other steps), a resource and optional
|
|
action name (defaults to the primary action if one is not provided).
|
|
|
|
Actions have several common options and some specific to their particular type.
|
|
See the [DSL documentation](dsl-ash-reactor.html) for
|
|
details.
|
|
|
|
### Action inputs
|
|
|
|
Ash actions take a map of input parameters which are usually a combination of
|
|
resource attributes and action arguments. You can provide these values as a
|
|
single map using the [`inputs` DSL entity](dsl-ash-reactor.html#reactor-action-inputs) with a map or keyword list which refers to Reactor inputs, results and hard-coded values via Reactor's [predefined template functions](https://hexdocs.pm/reactor/Reactor.Dsl.Argument.html#functions).
|
|
|
|
For action types that act on a specific resource (ie `update` and `destroy`) you can provide the value using the [`initial` DSL option](dsl-ash-reactor.html#reactor-update-initial).
|
|
|
|
#### Example
|
|
|
|
```elixir
|
|
input :blog_title
|
|
input :blog_body
|
|
input :author_email
|
|
|
|
read :get_author, MyBlog.Author, :get_author_by_email do
|
|
inputs %{email: input(:author_email)}
|
|
end
|
|
|
|
create :create_post, MyBlog.Post, :create do
|
|
inputs %{
|
|
title: input(:blog, [:title]),
|
|
body: input(:blog, [:body]),
|
|
author_id: result(:get_author, [:email])
|
|
}
|
|
end
|
|
|
|
update :author_post_count, MyBlog.Author, :update_post_count do
|
|
wait_for :create_post
|
|
initial result(:get_author)
|
|
end
|
|
|
|
return :create_post
|
|
```
|
|
|
|
## Handling failure.
|
|
|
|
Reactor is a saga executor, which means that when failure occurs it tries to
|
|
clean up any intermediate state left behind. By default the `create`, `update`
|
|
and `destroy` steps do not specify any behaviour for what to do when there is a
|
|
failure downstream in the reactor. This can be changed by providing both an
|
|
`undo_action` and changing the step's `undo` option to either
|
|
`:outside_transaction` or `:always` depending on your resource and datalayer
|
|
semantics.
|
|
|
|
### The `undo` option.
|
|
|
|
- `:never` - this is the default, and means that the reactor will never try and
|
|
undo the action's work. This is the most performant option, as it means that
|
|
the reactor doesn't need to store as many intermediate values.
|
|
- `:outside_transaction` - this option allows the step to decide at runtime
|
|
whether it should support undo based on whether the action is being run within
|
|
a transaction. If it is, then no undo is required because the transaction
|
|
will rollback.
|
|
- `:always` - this forces the step to always undo it's work on failure.
|
|
|
|
### The `undo_action` option.
|
|
|
|
The behaviour of the `undo_action` is action specific:
|
|
|
|
- For `create` actions, the `undo_action` should be the name of a `destroy`
|
|
action with no specific requirements.
|
|
- For `update` actions, the `undo_action` should also be an `update` action
|
|
which takes a `changeset` argument, which will contain the `Ash.Changeset`
|
|
which was used to execute the original update.
|
|
- For `destroy` actions, the `undo_action` should be the name of a `create`
|
|
action which takes a `record` argument, which will contain the
|
|
resource record which was used destroyed.
|
|
|
|
### Transactions
|
|
|
|
You can use the `transaction` step type to wrap a group of steps inside a data layer transaction, however the following caveats apply:
|
|
|
|
- All steps inside a transaction must happen in the same process, so the steps
|
|
inside the transaction will only ever be executed synchronously.
|
|
- Notifications will be sent only when the transaction is committed.
|
|
|
|
## Notifications
|
|
|
|
Because a reactor has transaction-like semantics notifications are automatically batched and only sent upon successful completion of the reactor.
|
|
|
|
## Running Reactors as an action
|
|
|
|
Currently the best way to expose a Reactor as an action is to use a [Generic Action](actions.html#generic-actions).
|
|
|
|
### Example
|
|
|
|
```elixir
|
|
action :run_reactor, :struct do
|
|
constraints instance_of: MyBlog.Post
|
|
|
|
argument :blog_title, :string, allow_nil?: false
|
|
argument :blog_body, :string, allow_nil?: false
|
|
argument :author_email, :ci_string, allow_nil?: false
|
|
|
|
run fn input, _context ->
|
|
Reactor.run(MyBlog.CreatePostReactor, input.arguments)
|
|
end
|
|
end
|
|
```
|