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

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

And 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}]
  # 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:

User.full_name("Jessie", "James", "-")
# or with a record as an argument
User.full_name(user)

This allows for running calculations without an instance of a resource, i.e Api.load(user, :full_name)