mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 13:33:20 +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>
152 lines
5.7 KiB
Markdown
152 lines
5.7 KiB
Markdown
# Code Interface
|
|
|
|
One of the ways that we interact with our resources is via hand-written code. The general pattern for that looks like building a query or a changeset for a given action, and calling it via functions like `Ash.read/2` and `Ash.create/2`. This, however, is just one way to use Ash, and is designed to help you build tools that work with resources, and to power things like `AshPhoenix.Form`, `AshGraphql.Resource` and `AshJsonApi.Resource`. When working with your resources in code, we generally want something more idiomatic and simple. For example, on a domain called `Helpdesk.Support`.
|
|
|
|
```elixir
|
|
resources do
|
|
resource Ticket do
|
|
define :open_ticket, args: [:subject], action: :open
|
|
end
|
|
end
|
|
```
|
|
|
|
This simple setup now allows you to open a ticket with `Helpdesk.Support.open_ticket(subject)`. You can cause it to raise errors instead of return them with `Helpdesk.Support.open!(subject)`. For information on the options and additional inputs these defined functions take, look at the generated function documentation, which you can do in iex with `h Helpdesk.Support.open_ticket`. For more information on the code interface, read the DSL documentation: `d:Ash.Domain.Dsl.resource.interfaces`.
|
|
|
|
## Code interfaces on the resource
|
|
|
|
You can define a code interface on individual resources as well, using the `code_interface` block. The DSL is the same as the DSL for defining it in the `domain`. For example:
|
|
|
|
```elixir
|
|
code_interface do
|
|
# the action open can be omitted because it matches the functon name
|
|
define :open, args: [:subject]
|
|
end
|
|
```
|
|
|
|
## Using the code interface
|
|
|
|
If the action is an update or destroy, it will take a record or a changeset as its _first_ argument.
|
|
If the action is a read action, it will take a starting query as an _opt in the last_ argument.
|
|
|
|
All functions will have an optional last argument that accepts options. See `Ash.Resource.Interface.interface_options/1` for valid options.
|
|
|
|
For reads:
|
|
|
|
- `:query` - a query to start the action with, can be used to filter/sort the results of the action.
|
|
|
|
For creates:
|
|
|
|
- `:changeset` - a changeset to start the action with
|
|
|
|
They will also have an optional second to last argument that is a freeform map to provide action input. It _must be a map_.
|
|
If it is a keyword list, it will be assumed that it is actually `options` (for convenience).
|
|
This allows for the following behaviour:
|
|
|
|
```elixir
|
|
# Because the 3rd argument is a keyword list, we use it as options
|
|
Accounts.register_user(username, password, [tenant: "organization_22"])
|
|
# Because the 3rd argument is a map, we use it as action input
|
|
Accounts.register_user(username, password, %{key: "val"})
|
|
# When all arguments are provided it is unambiguous
|
|
Accounts.register_user(username, password, %{key: "val"}, [tenant: "organization_22"])
|
|
```
|
|
|
|
## Calculations
|
|
|
|
Resource calculations can be run dynamically using `Ash.calculate/3`, but
|
|
you can also expose them using the code_interface with `define_calculation`.
|
|
|
|
For example:
|
|
|
|
```elixir
|
|
calculations do
|
|
calculate :full_name, :string, expr(first_name <> ^arg(:separator) <> last_name) do
|
|
argument :separator, :string do
|
|
allow_nil? false
|
|
default " "
|
|
end
|
|
end
|
|
end
|
|
|
|
# in your domain
|
|
resource User do
|
|
define_calculation :full_name, args: [:first_name, :last_name, {:optional, :separator}]
|
|
# or if you want to take a record as an argument
|
|
define_calculation :full_name, args: [:_record]
|
|
end
|
|
```
|
|
|
|
This could now be used like so:
|
|
|
|
```elixir
|
|
Accounts.full_name("Jessie", "James", "-")
|
|
# or with a record as an argument
|
|
Accounts.full_name(user)
|
|
```
|
|
|
|
This allows for running calculations without an instance of a resource, normally done via `Ash.load(user, :full_name)`
|
|
|
|
By default, configured args will be provided for any matching named reference _or_ argument. This is normally fine, but in the case that you have an argument and a reference with the same name, you can specify it by supplying `{:arg, :name}` and `{:ref, :name}`. For example:
|
|
|
|
```elixir
|
|
define_calculation :id_matches, args: [{:arg, :id}, {:ref, :id}]
|
|
```
|
|
|
|
To make arguments optional, wrap them in `{:optional, ..}`, for example:
|
|
|
|
```elixir
|
|
define_calculation :id_matches, args: [{:arg, :id}, {:optional, {:ref, :id}}]
|
|
```
|
|
|
|
## Bulk & atomic actions
|
|
|
|
### Bulk Updates & Destroys
|
|
|
|
Updates support a list, stream, or query as the first argument. This allows for bulk updates. In this mode, an `%Ash.BulkResult{}` is returned.
|
|
|
|
> ### Valid inputs {:.WARNING}
|
|
>
|
|
> You cannot provide "any enumerable", only lists, streams (a function or a %Stream{}), and queries. We have to be able to distinguish the input as a bulk input and not input to the action itself.
|
|
|
|
For example:
|
|
|
|
```elixir
|
|
Post
|
|
|> Ash.Query.filter(author_id == ^author_id)
|
|
|> MyApp.Blog.archive_post!()
|
|
# => %Ash.BulkResult{}
|
|
|
|
[%Post{}, %Post{}]
|
|
|> MyApp.Blog.destroy_post!()
|
|
# => %Ash.BulkResult{}
|
|
end
|
|
```
|
|
|
|
You can pass options to the bulk operation with the `bulk_options` option to your code interface function.
|
|
|
|
### Bulk Creates
|
|
|
|
For bulk creates, you can provide a list or stream of inputs. In this mode also, an `%Ash.BulkResult{}` is returned.
|
|
|
|
> ### Valid inputs {:.WARNING}
|
|
>
|
|
> You cannot provide "any enumerable", only lists, streams (a function or a %Stream{}). We have to be able to distinguish the input as a bulk input and not input to the action itself.
|
|
|
|
Any arguments on the code interface will be applied to _all_ inputs given as a list, and the arguments will come first.
|
|
|
|
```elixir
|
|
[%{title: "Post 1"}, %{title: "Post 2"}, ...]
|
|
# if `:special` is an action argument, it will be applied to all inputs
|
|
|> MyApp.Blog.create_post!(:special, bulk_options: [batch_size: 10])
|
|
```
|
|
|
|
### Returning streams from read actions
|
|
|
|
The `:stream?` option allows you to return a stream to be enumerated later.
|
|
|
|
For example:
|
|
|
|
```elixir
|
|
MyApp.Blog.my_posts(stream?: true, actor: me)
|
|
# => #Stream<...>
|
|
```
|