View Source Managing Relationships

In Ash, managing related data is done via Ash.Changeset.manage_relationship/4. There are various ways to leverage the functionality expressed there. If you are working with changesets directly, you can call that function. However, if you want that logic to be portable (e.g available in ash_graphql mutations and ash_json_api actions), then you want to use the following argument + change pattern:

actions do
  update :update do
    argument :add_comment, :map do
      allow_nil? false
    end

    argument :tags, {:array, :uuid} do
      allow_nil? false
    end

    # First argument is the name of the action argument to use
    # Second argument is the relationship to be managed
    # Third argument is options. For more, see `Ash.Changeset.manage_relationship/4`. This accepts the same options.
    change manage_relationship(:add_comment, :comments, type: :create)

    # Second argument can be omitted, as the argument name is the same as the relationship
    change manage_relationship(:tags, type: :append_and_remove)
  end
end

With this, those arguments can be used in action input:

post
|> Ash.Changeset.for_update(:update, tags: [tag1.id, tag2.id], add_comment: %{text: "comment text"})
|> MyApi.update!()

Argument Types

Notice how we provided a map as input to add_comment, and a list of UUIDs as an input to manage_relationship. When providing maps or lists of maps, you are generally just providing input that will eventually be passed into actions on the destination resource. However, you can also provide individual values or lists of values. By default, we assume that value maps to the primary key of the destination resource, but you can use the value_is_key option to modify that behavior. For example, if you wanted adding a comment to take a list of strings, you could say:

argument :add_comment, :string 

...
change manage_relationship(:add_comment, :comments, type: :create, value_is_key: :text)

And then you could use it like so:

post
|> Ash.Changeset.for_update(:update, tags: [tag1.id, tag2.id], add_comment: "comment text")
|> MyApi.update!()

Derived behavior

Determining what will happen when managing related data can be complicated, as the nature of the problem itself is quite complicated. In some simple cases, like type: :create, there may be only one action that will be called. But in order to support all of the various ways that related resources may need to be managed, Ash provides a very rich set of options to determine what happens with the provided input. Tools like AshPhoenix.Form can look at your arguments that have a corresponding manage_relationship change, and derive the structure of those nested forms. Tools like AshGraphql can derive complex input objects to allow manipulating those relationships over a graphql Api. This all works because the options are, ultimately, quite explicit. It can be determined exactly what actions might be called, and therefore what input could be needed.