View Source 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 dispatching it to the api using things like MyApi.read/3 and MyApi.create/3. 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 resource called Helpdesk.Support.Ticket:
code_interface do
define_for Helpdesk.Support
define :open, args: [:subject]
endThis simple setup now allows you to open a ticket with Helpdesk.Support.Ticket.open(subject). You can cause it to raise errors instead of return them with Helpdesk.Support.Ticket.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.Ticket.open. For more information on the code interface, read the DSL documentation: Ash.Resource.Dsl.code_interface.
define_for-and-define_interface
define_for and define_interface
Notice how we included a specific Api module using define_for above. Without this, no functions will be defined in the resource. This is because you might want to define the interface for multiple resources in a single module. While we encourage the use of define_for Api, it is not the only way to do it. You could also do something like this:
defmodule MyApp.MyApi.Interface do
require Ash.CodeInterface
Ash.CodeInterface.define_interface(MyApp.MyApi, MyApp.Resource1)
Ash.CodeInterface.define_interface(MyApp.MyApi, MyApp.Resource2)
endAnd then call functions on MyApp.MyApi.Interface instead.
using-the-code-interface
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:
# Because the 3rd argument is a keyword list, we use it as options
Api.register_user(username, password, [tenant: "organization_22"])
# Because the 3rd argument is a map, we use it as action input
Api.register_user(username, password, %{key: "val"})
# When all arguments are provided it is unambiguous
Api.register_user(username, password, %{key: "val"}, [tenant: "organization_22"])
calculations
Calculations
Resource calculations can be run dynamically using YourApi.calculate/3, but
you can also expose them using the code_interface with define_calculation.
For example:
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
code_interface do
define_for YourApi
define_calculation :full_name, args: [:first_name, :last_name, {:optional, :separator}]
endThis could now be used like so:
User.full_name("Jessie", "James", "-")This allows for running calculations without an instance of a resource, i.e Api.load(user, :full_name)