#
View Source Ash.Changeset (ash v2.4.22)

Changesets are used to create and update data in Ash.

Create a changeset with `new/1` or `new/2`, and alter the attributes and relationships using the functions provided in this module. Nothing in this module actually incurs changes in a data layer. To commit a changeset, see `Ash.Api.create/2` and `Ash.Api.update/2`.

See the action DSL documentation for more.

# Link to this section Summary

## Types

around_callback()
around_result()
manage_relationship_type()
t()

## Functions

add_error(changeset, errors, path \\ [])

Adds an error to the changesets errors list, and marks the change as `valid?: false`

after_action(changeset, func, opts \\ [])

Adds an after_action hook to the changeset.

append_to_relationship(changeset, relationship, record_or_records, opts \\ []) deprecated

Appends a record or a list of records to a relationship.

apply_attributes(changeset, opts \\ [])

Returns the original data with attribute changes merged, if the changeset is valid.

around_action(changeset, func)

Adds an around_action hook to the changeset.

before_action(changeset, func, opts \\ [])

Adds a before_action hook to the changeset.

change_attribute(changeset, attribute, value)

Adds a change to the changeset, unless the value matches the existing value

change_attributes(changeset, changes)

Calls `change_attribute/3` for each key/value pair provided

change_default_attribute(changeset, attribute, value)

The same as `change_attribute`, but annotates that the attribute is currently holding a default value.

change_new_attribute(changeset, attribute, value)

Change an attribute only if is not currently being changed

change_new_attribute_lazy(changeset, attribute, func)

Change an attribute if is not currently being changed, by calling the provided function

changing_attribute?(changeset, attribute)

Returns true if an attribute exists in the changes

changing_attributes?(changeset)

Returns true if any attributes on the resource are being changed.

changing_relationship?(changeset, relationship)

Returns true if a relationship exists in the changes

clear_change(changeset, field)

Clears an attribute or relationship change off of the changeset

delete_argument(changeset, argument_or_arguments)

Remove an argument from the changeset

deselect(changeset, fields)

Ensure the the specified attributes are `nil` in the changeset results.

ensure_selected(changeset, fields)

Ensures that the given attributes are selected.

fetch_argument(changeset, argument)

fetches the value of an argument provided to the changeset or `:error`

fetch_argument_or_change(changeset, attribute)

Gets the value of an argument provided to the changeset, falling back to `Ash.Changeset.fetch_change/2` if nothing was provided

fetch_change(changeset, attribute)

Gets the new value for an attribute, or `:error` if it is not being changed

for_action(initial, action, params \\ %{}, opts \\ [])

Constructs a changeset for a given action, and validates it.

for_create(initial, action, params \\ %{}, opts \\ [])

Constructs a changeset for a given create action, and validates it.

for_destroy(initial, action_name, params \\ %{}, opts \\ [])

Constructs a changeset for a given destroy action, and validates it.

for_update(initial, action, params \\ %{}, opts \\ [])

Constructs a changeset for a given update action, and validates it.

force_change_attribute(changeset, attribute, value)

Changes an attribute even if it isn't writable

force_change_attributes(changeset, changes)

Calls `force_change_attribute/3` for each key/value pair provided

force_change_new_attribute(changeset, attribute, value)

Force change an attribute if it is not currently being changed

force_change_new_attribute_lazy(changeset, attribute, func)

Force change an attribute if it is not currently being changed, by calling the provided function

get_argument(changeset, argument)

Gets the value of an argument provided to the changeset

get_argument_or_attribute(changeset, attribute)

Gets the value of an argument provided to the changeset, falling back to `Ash.Changeset.get_attribute/2` if nothing was provided

get_attribute(changeset, attribute)

Gets the changing value or the original value of an attribute

get_data(changeset, attribute)

Gets the original value for an attribute

handle_errors(changeset, func)

Sets a custom error handler on the changeset.

manage_relationship(changeset, relationship, input, opts \\ [])

Manages the related records by creating, updating, or destroying them as necessary.

manage_relationship_opts(atom)
new(resource, params \\ %{})

Returns a new changeset over a resource. Prefer `for_action` or `for_create`, etc. over this function if possible.

put_context(changeset, key, value)

Puts a key/value in the changeset context that can be used later

remove_from_relationship(changeset, relationship, record_or_records, opts \\ []) deprecated

Removes a record or a list of records to a relationship.

replace_relationship(changeset, relationship, record_or_records, opts \\ []) deprecated

Alias for

select(changeset, fields, opts \\ [])

Ensure that only the specified attributes are present in the results.

selecting?(changeset, field)
set_argument(changeset, argument, value)

Add an argument to the changeset, which will be provided to the action

set_arguments(changeset, map)

Merge a map of arguments to the arguments list

set_context(changeset, map)

Deep merges the provided map into the changeset context that can be used later

set_on_upsert(changeset, upsert_keys)
set_tenant(changeset, tenant)
timeout(changeset, timeout, default \\ nil)
with_hooks(changeset, func)

Wraps a function in the before/after action hooks of a changeset.

# Link to this section Types

Link to this type

# around_callback()

View Source

    @type around_callback() :: (t() -> around_result())

Link to this type

# around_result()

View Source

    @type around_result() ::
      {:ok, Ash.Resource.record(), t(),
       %{notifications: [Ash.Notifier.Notification.t()]}}
      | {:error, Ash.Error.t()}

Link to this type

# manage_relationship_type()

View Source

    @type manage_relationship_type() ::
      :append_and_remove | :append | :remove | :direct_control | :create

Link to this type

# t()

View Source

    @type t() :: %Ash.Changeset{
      __validated_for_action__: term(),
      action: term(),
      action_failed?: term(),
      action_type: term(),
      after_action: term(),
      api: term(),
      arguments: term(),
      around_action: term(),
      attributes: term(),
      before_action: term(),
      change_dependencies: term(),
      context: term(),
      data: Ash.Resource.record(),
      defaults: term(),
      errors: term(),
      handle_errors: term(),
      params: term(),
      relationships: term(),
      resource: term(),
      select: term(),
      tenant: term(),
      timeout: term(),
      valid?: term()
    }

# Link to this section Functions

Link to this function

# add_error(changeset, errors, path \\ [])

View Source

Adds an error to the changesets errors list, and marks the change as `valid?: false`

Link to this function

# after_action(changeset, func, opts \\ [])

View Source

    @spec after_action(
      t(),
      (t(), Ash.Resource.record() ->
         {:ok, Ash.Resource.record()}
         | {:ok, Ash.Resource.record(), [Ash.Notifier.Notification.t()]}
         | {:error, term()}),
      Keyword.t()
    ) :: t()

Adds an after_action hook to the changeset.

Provide the option `prepend?: true` to place the hook before all other hooks instead of after.

Link to this function

# append_to_relationship(changeset, relationship, record_or_records, opts \\ [])

View Source
This function is deprecated. Use manage_relationship/4 instead.

    @spec append_to_relationship(
      t(),
      atom(),
      Ash.Resource.record()
      | map()
      | term()
      | [Ash.Resource.record() | map() | term()],
      Keyword.t()
    ) :: t()

Appends a record or a list of records to a relationship.

Alias for:

    manage_relationship(changeset, relationship, input,
      on_lookup: :relate, # If a record is not in the relationship, and can be found, relate it
      on_no_match: :error, # If a record is not found in the relationship or the database, we error
      on_match: :ignore, # If a record is found in the relationship we don't change it
      on_missing: :ignore, # If a record is not found in the input, we ignore it
    )

Provide `opts` to customize/override the behavior.

Link to this function

# apply_attributes(changeset, opts \\ [])

View Source

    @spec apply_attributes(t(), opts :: Keyword.t()) ::
      {:ok, Ash.Resource.record()} | {:error, t()}

Returns the original data with attribute changes merged, if the changeset is valid.

Options:

- force? - applies current attributes even if the changeset is not valid

Link to this function

# around_action(changeset, func)

View Source

    @spec around_action(t(), (t(), around_callback() -> around_result())) :: t()

Adds an around_action hook to the changeset.

Your function will get the changeset, and a callback that must be called with a changeset (that may be modified). The callback will return `{:ok, result, instructions}` or `{:error, error}`. You can modify these values, but the return value must be one of those types. Instructions contains the notifications in its `notifications` key, i.e `%{notifications: [%Ash.Resource.Notification{}, ...]}`.

The around_action calls happen first, and then (after they each resolve their callbacks) the `before_action` hooks are called, followed by the action itself occurring at the data layer and then the `after_action` hooks being run. Then, the code that appeared after the callbacks were called is then run.

For example:

    changeset
    |> Ash.Changeset.around_action(fn changeset, callback ->
      IO.puts("first around: before")
      result = callback.(changeset)
      IO.puts("first around: after")
    end)
    |> Ash.Changeset.around_action(fn changeset, callback ->
      IO.puts("second around: before")
      result = callback.(changeset)
      IO.puts("second around: after")
    end)
    |> Ash.Changeset.before_action(fn changeset ->
      IO.puts("first before")
      changeset
    end)
    |> Ash.Changeset.before_action(fn changeset ->
      IO.puts("second before")
      changeset
    end)
    |> Ash.Changeset.after_action(fn changeset, result ->
      IO.puts("first after")
      {:ok, result}
    end)
    |> Ash.Changeset.after_action(fn changeset ->
      IO.puts("second after")
      {:ok, result}
    end)

This would print:

    first around: before
    second around: before
    first before
    second before
                 <-- action happens here
    first after
    second after
    second around: after <-- Notice that because of the callbacks, the after of the around hooks is reversed from the before
    first around: after

Warning: using this without understanding how it works can cause big problems. You must call the callback function that is provided to your hook, and the return value must contain the same structure that was given to you, i.e `{:ok, result_of_action, instructions}`.

You can almost always get the same effect by using `before_action`, setting some context on the changeset and reading it out in an `after_action` hook.

Link to this function

# before_action(changeset, func, opts \\ [])

View Source

    @spec before_action(
      t(),
      (t() -> t() | {t(), %{notifications: [Ash.Notifier.Notification.t()]}}),
      Keyword.t()
    ) :: t()

Adds a before_action hook to the changeset.

Provide the option `append?: true` to place the hook after all other hooks instead of before.

Link to this function

# change_attribute(changeset, attribute, value)

View Source

    @spec change_attribute(t(), atom(), any()) :: t()

Adds a change to the changeset, unless the value matches the existing value

Link to this function

# change_attributes(changeset, changes)

View Source

    @spec change_attributes(t(), map() | Keyword.t()) :: t()

Calls `change_attribute/3` for each key/value pair provided

Link to this function

# change_default_attribute(changeset, attribute, value)

View Source

    @spec change_default_attribute(t(), atom(), any()) :: t()

The same as `change_attribute`, but annotates that the attribute is currently holding a default value.

This information can be used in changes to see if a value was explicitly set or if it was set by being the default. Additionally, this is used in `upsert` actions to not overwrite existing values with the default

Link to this function

# change_new_attribute(changeset, attribute, value)

View Source

    @spec change_new_attribute(t(), atom(), term()) :: t()

Change an attribute only if is not currently being changed

Link to this function

# change_new_attribute_lazy(changeset, attribute, func)

View Source

    @spec change_new_attribute_lazy(t(), atom(), (() -> any())) :: t()

Change an attribute if is not currently being changed, by calling the provided function

Use this if you want to only perform some expensive calculation for an attribute value only if there isn't already a change for that attribute

Link to this function

# changing_attribute?(changeset, attribute)

View Source

    @spec changing_attribute?(t(), atom()) :: boolean()

Returns true if an attribute exists in the changes

Link to this function

# changing_attributes?(changeset)

View Source

    @spec changing_attributes?(t()) :: boolean()

Returns true if any attributes on the resource are being changed.

Link to this function

# changing_relationship?(changeset, relationship)

View Source

    @spec changing_relationship?(t(), atom()) :: boolean()

Returns true if a relationship exists in the changes

Link to this function

# clear_change(changeset, field)

View Source

Clears an attribute or relationship change off of the changeset

Link to this function

# delete_argument(changeset, argument_or_arguments)

View Source

Remove an argument from the changeset

Link to this function

# deselect(changeset, fields)

View Source

Ensure the the specified attributes are `nil` in the changeset results.

Link to this function

# ensure_selected(changeset, fields)

View Source

Ensures that the given attributes are selected.

The first call to `select/2` will limit the fields to only the provided fields. Use `ensure_selected/2` to say "select this field (or these fields) without deselecting anything else".

See `select/2` for more.

Link to this function

# fetch_argument(changeset, argument)

View Source

    @spec fetch_argument(t(), atom()) :: {:ok, term()} | :error

fetches the value of an argument provided to the changeset or `:error`

Link to this function

# fetch_argument_or_change(changeset, attribute)

View Source

    @spec fetch_argument_or_change(t(), atom()) :: {:ok, any()} | :error

Gets the value of an argument provided to the changeset, falling back to `Ash.Changeset.fetch_change/2` if nothing was provided

Link to this function

# fetch_change(changeset, attribute)

View Source

    @spec fetch_change(t(), atom()) :: {:ok, any()} | :error

Gets the new value for an attribute, or `:error` if it is not being changed

Link to this function

# for_action(initial, action, params \\ %{}, opts \\ [])

View Source

Constructs a changeset for a given action, and validates it.

Calls `for_create/4`, `for_update/4` or `for_destroy/4` based on the type of action passed in.

See those functions for more explanation.

Link to this function

# for_create(initial, action, params \\ %{}, opts \\ [])

View Source

Constructs a changeset for a given create action, and validates it.

Anything that is modified prior to `for_create/4` is validated against the rules of the action, while anything after it is not. This runs any `change`s contained on your action. To have your logic execute only during the action, you can use `after_action/2` or `before_action/2`.

Multitenancy is not validated until an action is called. This allows you to avoid specifying a tenant until just before calling the api action.

###

params

Params

`params` may be attributes, relationships, or arguments. You can safely pass user/form input directly into this function. Only public attributes and relationships are supported. If you want to change private attributes as well, see the Customization section below. `params` are stored directly as given in the `params` field of the changeset, which is used

###

opts

Opts

- `:require?` (`boolean/0`) - If set to `true`, values are only required when the action is run (instead of immediately). The default value is `false`.

- `:actor` (`term/0`) - set the actor, which can be used in any `Ash.Resource.Change`s configured on the action. (in the `context` argument)

- `:authorize?` (`term/0`) - set authorize?, which can be used in any `Ash.Resource.Change`s configured on the action. (in the `context` argument)

- `:tracer` (`atom/0`) - A tracer to use. Will be carried over to the action. For more information see `Ash.Tracer`.

- `:tenant` (`term/0`) - set the tenant on the changeset

###

customization

Customization

A changeset can be provided as the first argument, instead of a resource, to allow setting specific attributes ahead of time.

For example:

    MyResource
    |> Ash.Changeset.new()
    |> Ash.Changeset.change_attribute(:foo, 1)
    |> Ash.Changeset.for_create(:create, ...opts)

Once a changeset has been validated by `for_create/4` (or `for_update/4`), it isn't validated again in the action. New changes added are validated individually, though. This allows you to create a changeset according to a given action, and then add custom changes if necessary.

Link to this function

# for_destroy(initial, action_name, params \\ %{}, opts \\ [])

View Source

Constructs a changeset for a given destroy action, and validates it.

###

opts

Opts

- `:actor` - set the actor, which can be used in any `Ash.Resource.Change`s configured on the action. (in the `context` argument)
- `:tenant` - set the tenant on the changeset

Anything that is modified prior to `for_destroy/4` is validated against the rules of the action, while anything after it is not.

Once a changeset has been validated by `for_destroy/4`, it isn't validated again in the action. New changes added are validated individually, though. This allows you to create a changeset according to a given action, and then add custom changes if necessary.

Link to this function

# for_update(initial, action, params \\ %{}, opts \\ [])

View Source

Constructs a changeset for a given update action, and validates it.

Anything that is modified prior to `for_update/4` is validated against the rules of the action, while anything after it is not.

See `for_create/4` for more information

Link to this function

# force_change_attribute(changeset, attribute, value)

View Source

    @spec force_change_attribute(t(), atom(), any()) :: t()

Changes an attribute even if it isn't writable

Link to this function

# force_change_attributes(changeset, changes)

View Source

    @spec force_change_attributes(t(), map() | Keyword.t()) :: t()

Calls `force_change_attribute/3` for each key/value pair provided

Link to this function

# force_change_new_attribute(changeset, attribute, value)

View Source

    @spec force_change_new_attribute(t(), atom(), term()) :: t()

Force change an attribute if it is not currently being changed

See `change_new_attribute/3` for more.

Link to this function

# force_change_new_attribute_lazy(changeset, attribute, func)

View Source

    @spec force_change_new_attribute_lazy(t(), atom(), (() -> any())) :: t()

Force change an attribute if it is not currently being changed, by calling the provided function

See `change_new_attribute_lazy/3` for more.

Link to this function

# get_argument(changeset, argument)

View Source

    @spec get_argument(t(), atom()) :: term()

Gets the value of an argument provided to the changeset

Link to this function

# get_argument_or_attribute(changeset, attribute)

View Source

    @spec get_argument_or_attribute(t(), atom()) :: term()

Gets the value of an argument provided to the changeset, falling back to `Ash.Changeset.get_attribute/2` if nothing was provided

Link to this function

# get_attribute(changeset, attribute)

View Source

    @spec get_attribute(t(), atom()) :: term()

Gets the changing value or the original value of an attribute

Link to this function

# get_data(changeset, attribute)

View Source

    @spec get_data(t(), atom()) :: term()

Gets the original value for an attribute

Link to this function

# handle_errors(changeset, func)

View Source

    @spec handle_errors(
      t(),
      (t(), error :: term() ->
         :ignore | t() | (error :: term()) | {error :: term(), t()})
      | {module(), atom(), [term()]}
    ) :: t()

Sets a custom error handler on the changeset.

The error handler should be a two argument function or an mfa, in which case the first two arguments will be set to the changeset and the error, w/ the supplied arguments following those.

Any errors generated are passed to `handle_errors`, which can return any of the following:

- `:ignore` - the error is discarded, and the changeset is not marked as invalid
- `changeset` - a new (or the same) changeset. The error is not added (you'll want to add an error yourself), but the changeset is marked as invalid.
- `{changeset, error}` - a new (or the same) error and changeset. The error is added to the changeset, and the changeset is marked as invalid.
- `anything_else` - is treated as a new, transformed version of the error. The result is added as an error to the changeset, and the changeset is marked as invalid.

Link to this function

# manage_relationship(changeset, relationship, input, opts \\ [])

View Source

Manages the related records by creating, updating, or destroying them as necessary.

Keep in mind that the default values for all `on_*` are `:ignore`, meaning nothing will happen unless you provide instructions.

The input provided to `manage_relationship` should be a map, in the case of to_one relationships, or a list of maps in the case of to_many relationships. The following steps are followed for each input provided:

- The input is checked against the currently related records to find any matches. The primary key and unique identities are used to find matches.
- For any input that had a match in the current relationship, the `:on_match` behavior is triggered
- For any input that does not have a match:
  - if there is `on_lookup` behavior:
    - we try to find the record in the data layer.
    - if the record is found, the on_lookup behavior is triggered
    - if the record is not found, the `on_no_match` behavior is triggered
  - if there is no `on_lookup` behavior:
    - the `on_no_match` behavior is triggered
- finally, for any records present in the current relationship that had no match in the input, the `on_missing` behavior is triggered

##

options

Options

- `:type` - If the `type` is specified, the default values of each option is modified to match that `type` of operation.
  This allows for specifying certain operations much more succinctly. The defaults that are modified are listed below

  ##

  append_and_remove

  `:append_and_remove`

  [

      on_lookup: :relate,
      on_no_match: :error,
      on_match: :ignore,
      on_missing: :unrelate

  ]

  ##

  append

  `:append`

  [

      on_lookup: :relate,
      on_no_match: :error,
      on_match: :ignore,
      on_missing: :ignore

  ]

  ##

  remove

  `:remove`

  [

      on_no_match: :error,
      on_match: :unrelate,
      on_missing: :ignore

  ]

  ##

  direct_control

  `:direct_control`

  [

      on_lookup: :ignore,
      on_no_match: :create,
      on_match: :update,
      on_missing: :destroy

  ]

  ##

  create

  `:create`

  [

      on_no_match: :create,
      on_match: :ignore

  ]

- `:authorize?` (`boolean/0`) - Authorize reads and changes to the destination records, if the primary change is being authorized as well. The default value is `true`.

- `:eager_validate_with` (`atom/0`) - Validates that any referenced entities exist before the action is being performed, using the provided api for the read. The default value is `false`.

- `:on_no_match` (`term/0`) - instructions for handling records where no matching record existed in the relationship

      * `:ignore`(default) - those inputs are ignored
      * `:match` - For "has_one" and "belongs_to" only, any input is treated as a match for an existing value. For has_many and many_to_many, this is the same as :ignore.
      * `:create` - the records are created using the destination's primary create action
      * `{:create, :action_name}` - the records are created using the specified action on the destination resource
      * `{:create, :action_name, :join_table_action_name, [:list, :of, :join_table, :params]}` - Same as `{:create, :action_name}` but takes
          the list of params specified out and applies them when creating the join record, with the provided join_table_action_name.
      * `:error`  - an eror is returned indicating that a record would have been created
        *  If `on_lookup` is set, and the data contained a primary key or identity, then the error is a `NotFound` error
        * Otherwise, an `InvalidRelationship` error is returned The default value is `:ignore`.

- `:value_is_key` (`atom/0`) - Configures what key to use when a single value is provided.
  This is useful when you use things like a list of strings i.e `friend_emails` to manage the relationship, instead of a list of maps.
  By default, we assume it is the primary key of the destination resource, unless it is a composite primary key.

- `:identity_priority` (list of `atom/0`) - The list, in priority order, of identities to use when looking up records for `on_lookup`, and matching records with `on_match`.
  Use `:_primary_key` to prioritize checking a match with the primary key. All identities, along with `:_primary_key` are checked regardless, this only allows ensuring that some are checked first. The default value is `[:_primary_key]`.

- `:use_identities` (list of `atom/0`) - A list of identities that may be used to look up and compare records. Use `:_primary_key` to include the primary key. This does not determine the order that they are checked, use `identity_priority` for that. By default, all are used.

- `:on_lookup` (`term/0`) - Before creating a record(because no match was found in the relationship), the record can be looked up and related.

      * `:ignore`(default) - Does not look for existing entries (matches in the current relationship are still considered updates)
      * `:relate` - Same as calling `{:relate, primary_action_name}`
      * `{:relate, :action_name}` - the records are looked up by primary key/the first identity that is found (using the primary read action), and related. The action should be:
          * many_to_many - a create action on the join resource
          * has_many - an update action on the destination resource
          * has_one - an update action on the destination resource
          * belongs_to - an update action on the source resource
      * `{:relate, :action_name, :read_action_name}` - Same as the above, but customizes the read action called to search for matches.
      * `:relate_and_update` - Same as `:relate`, but the remaining parameters from the lookup are passed into the action that is used to change the relationship key
      * `{:relate_and_update, :action_name}` - Same as the above, but customizes the action used. The action should be:
          * many_to_many - a create action on the join resource
          * has_many - an update action on the destination resource
          * has_one - an update action on the destination resource
          * belongs_to - an update action on the source resource
      * `{:relate_and_update, :action_name, :read_action_name}` - Same as the above, but customizes the read action called to search for matches.
      * `{:relate_and_update, :action_name, :read_action_name, [:list, :of, :join_table, :params]}` - Same as the above, but uses the provided list of parameters when creating
         the join row (only relevant for many to many relationships). Use `:all` to *only* update the join record, and pass all parameters to its action The default value is `:ignore`.

- `:on_match` (`term/0`) - instructions for handling records where a matching record existed in the relationship already

      * `:ignore`(default) - those inputs are ignored
      * `:update` - the record is updated using the destination's primary update action
      * `{:update, :action_name}` - the record is updated using the specified action on the destination resource
      * `{:update, :action_name, :join_table_action_name, [:list, :of, :params]}` - Same as `{:update, :action_name}` but takes
          the list of params specified out and applies them as an update to the join record (only valid for many to many).
      * `{:destroy, :action_name}` - the record is destroyed using the specified action on the destination resource. The action should be:
        * many_to_many - a destroy action on the join record
        * has_many - a destroy action on the destination resource
        * has_one - a destroy action on the destination resource
        * belongs_to - a destroy action on the destination resource
      * `:error`  - an eror is returned indicating that a record would have been updated
      * `:no_match` - ignores the primary key match and follows the on_no_match instructions with these records instead.
      * `:unrelate` - the related item is not destroyed, but the data is "unrelated", making this behave like `remove_from_relationship/3`. The action should be:
        * many_to_many - the join resource row is destroyed
        * has_many - the destination_attribute (on the related record) is set to `nil`
        * has_one - the destination_attribute (on the related record) is set to `nil`
        * belongs_to - the source_attribute (on this record) is set to `nil`
      * `{:unrelate, :action_name}` - the record is unrelated using the provided update action. The action should be:
        * many_to_many - a destroy action on the join resource
        * has_many - an update action on the destination resource
        * has_one - an update action on the destination resource
        * belongs_to - an update action on the source resource The default value is `:ignore`.

- `:on_missing` (`term/0`) - instructions for handling records that existed in the current relationship but not in the input

      * `:ignore`(default) - those inputs are ignored
      * `:destroy` - the record is destroyed using the destination's primary destroy action
      * `{:destroy, :action_name}` - the record is destroyed using the specified action on the destination resource
      * `{:destroy, :action_name, :join_resource_action_name, [:join, :keys]}` - the record is destroyed using the specified action on the destination resource,
        but first the join resource is destroyed with its specified action
      * `:error`  - an error is returned indicating that a record would have been updated
      * `:unrelate` - the related item is not destroyed, but the data is "unrelated", making this behave like `remove_from_relationship/3`. The action should be:
        * many_to_many - the join resource row is destroyed
        * has_many - the destination_attribute (on the related record) is set to `nil`
        * has_one - the destination_attribute (on the related record) is set to `nil`
        * belongs_to - the source_attribute (on this record) is set to `nil`
      * `{:unrelate, :action_name}` - the record is unrelated using the provided update action. The action should be:
        * many_to_many - a destroy action on the join resource
        * has_many - an update action on the destination resource
        * has_one - an update action on the destination resource
        * belongs_to - an update action on the source resource The default value is `:ignore`.

- `:error_path` (`term/0`) - By default, errors added to the changeset will use the path `[:relationship_name]`, or `[:relationship_name, <index>]`. If you want to modify this path, you can specify `error_path`, e.g if had a `change` on an action that takes an argument and uses that argument data to call `manage_relationship`, you may want any generated errors to appear under the name of that argument, so you could specify `error_path: :argument_name` when calling `manage_relationship`.

- `:meta` (`term/0`) - Freeform data that will be retained along with the options, which can be used to track/manage the changes that are added to the `relationships` key.

- `:ignore?` (`term/0`) - This tells Ash to ignore the provided inputs when actually running the action. This can be useful for building up a set of instructions that you intend to handle manually The default value is `false`.

Each call to this function adds new records that will be handled according to their options. For example, if you tracked "tags to add" and "tags to remove" in separate fields, you could input them like so:

    changeset
    |> Ash.Changeset.manage_relationship(
      :tags,
      [%{name: "backend"}],
      on_lookup: :relate, #relate that tag if it exists in the database
      on_no_match: :error # error if a tag with that name doesn't exist
    )
    |> Ash.Changeset.manage_relationship(
      :tags,
      [%{name: "frontend"}],
      on_no_match: :error, # error if a tag with that name doesn't exist in the relationship
      on_match: :unrelate # if a tag with that name is related, unrelate it
    )

When calling this multiple times with the `on_missing` option set, the list of records that are considered missing are checked after each set of inputs is processed. For example, if you manage the relationship once with `on_missing: :unrelate`, the records missing from your input will be removed, and then your next call to `manage_relationship` will be resolved (with those records unrelated). For this reason, it is suggested that you don't call this function multiple times with an `on_missing` instruction, as you may be surprised by the result.

If you want the input to update existing entities, you need to ensure that the primary key (or unique identity) is provided as part of the input. See the example below:

    changeset
    |> Ash.Changeset.manage_relationship(
      :comments,
      [%{rating: 10, contents: "foo"}],
      on_no_match: {:create, :create_action},
      on_missing: :ignore
    )
    |> Ash.Changeset.manage_relationship(
      :comments,
      [%{id: 10, rating: 10, contents: "foo"}],
      on_match: {:update, :update_action},
      on_no_match: {:create, :create_action})

This is a simple way to manage a relationship. If you need custom behavior, you can customize the action that is called, which allows you to add arguments/changes. However, at some point you may want to forego this function and make the changes yourself. For example:

    input = [%{id: 10, rating: 10, contents: "foo"}]

    changeset
    |> Ash.Changeset.after_action(fn _changeset, result ->
      # An example of updating comments based on a result of other changes
      for comment <- input do
        comment = MyApi.get(Comment, comment.id)

        comment
        |> Map.update(:rating, 0, &(&1 * result.rating_weight))
        |> MyApi.update!()
      end

      {:ok, result}
    end)

##

using-records-as-input

Using records as input

Records can be supplied as the input values. If you do:

- if it would be looked up due to `on_lookup`, the record is used as-is
- if it would be created due to `on_no_match`, the record is used as-is
- Instead of specifying `join_keys`, those keys must go in `__metadata__.join_keys`. If `join_keys` is specified in the options, it is ignored.

For example:

    post1 =
      changeset
      |> Api.create!()
      |> Ash.Resource.put_metadata(:join_keys, %{type: "a"})

    post1 =
      changeset2
      |> Api.create!()
      |> Ash.Resource.put_metadata(:join_keys, %{type: "b"})

    author = Api.create!(author_changeset)

    Ash.Changeset.manage_relationship(
      author,
      :posts,
      [post1, post2],
      on_lookup: :relate
    )

Link to this function

# manage_relationship_opts(atom)

View Source

    @spec manage_relationship_opts(manage_relationship_type()) :: Keyword.t()

Link to this function

# new(resource, params \\ %{})

View Source

    @spec new(Ash.Resource.t() | Ash.Resource.record(), params :: map()) :: t()

Returns a new changeset over a resource. Prefer `for_action` or `for_create`, etc. over this function if possible.

Warning: You almost always want to use `for_action/4` (or `for_create`, etc...)

You can use this to start a changeset and make a few changes prior to calling `for_action`. For example:

    Resource
    |> Ash.Changeset.new()
    |> Ash.Changeset.change_attribute(:name, "foobar")
    |> Ash.Changeset.for_action(...)

Link to this function

# put_context(changeset, key, value)

View Source

    @spec put_context(t(), atom(), term()) :: t()

Puts a key/value in the changeset context that can be used later

Do not use the `private` key in your custom context, as that is reserved for internal use.

Link to this function

# remove_from_relationship(changeset, relationship, record_or_records, opts \\ [])

View Source
This function is deprecated. Use manage_relationship/4 instead.

    @spec remove_from_relationship(
      t(),
      atom(),
      Ash.Resource.record()
      | map()
      | term()
      | [Ash.Resource.record() | map() | term()],
      Keyword.t()
    ) :: t()

Removes a record or a list of records to a relationship.

Alias for:

      manage_relationship(changeset, relationship, record_or_records,
        on_no_match: :error, # If a record is not found in the relationship, we error
        on_match: :unrelate, # If a record is found in the relationship we unrelate it
        on_missing: :ignore, # If a record is not found in the relationship
        authorize?: false
      )

Link to this function

# replace_relationship(changeset, relationship, record_or_records, opts \\ [])

View Source
This function is deprecated. Use manage_relationship/4 instead.

    @spec replace_relationship(
      t(),
      atom(),
      Ash.Resource.record()
      | map()
      | term()
      | [Ash.Resource.record() | map() | term()]
      | nil,
      Keyword.t()
    ) :: t()

Alias for:

    manage_relationship(
      changeset,
      relationship,
      record_or_records,
      on_lookup: :relate, # If a record is not found in the relationship, but is found in the database, relate it and apply the input as an update
      on_no_match: :error, # If a record is not found in the relationship or the database, we error
      on_match: :ignore, # If a record is found in the relationship we make no changes to it
      on_missing: :unrelate, # If a record is not found in the relationship, we unrelate it
      authorize?: false
    )

Link to this function

# select(changeset, fields, opts \\ [])

View Source

Ensure that only the specified attributes are present in the results.

The first call to `select/2` will replace the default behavior of selecting all attributes. Subsequent calls to `select/2` will combine the provided fields unless the `replace?` option is provided with a value of `true`.

If a field has been deselected, selecting it again will override that (because a single list of fields is tracked for selection)

Primary key attributes always selected and cannot be deselected.

When attempting to load a relationship (or manage it with `Ash.Changeset.manage_relationship/3`), if the source field is not selected on the query/provided data an error will be produced. If loading a relationship with a query, an error is produced if the query does not select the destination field of the relationship.

Datalayers currently are not notified of the `select` for a changeset(unlike queries), and creates/updates select all fields when they are performed. A select provided on a changeset sets the unselected fields to `nil` before returning the result.

Use `ensure_selected/2` if you wish to make sure a field has been selected, without deselecting any other fields.

Link to this function

# selecting?(changeset, field)

View Source
Link to this function

# set_argument(changeset, argument, value)

View Source

Add an argument to the changeset, which will be provided to the action

Link to this function

# set_arguments(changeset, map)

View Source

Merge a map of arguments to the arguments list

Link to this function

# set_context(changeset, map)

View Source

    @spec set_context(t(), map() | nil) :: t()

Deep merges the provided map into the changeset context that can be used later

Do not use the `private` key in your custom context, as that is reserved for internal use.

Link to this function

# set_on_upsert(changeset, upsert_keys)

View Source

    @spec set_on_upsert(t(), [atom()]) :: Keyword.t()

Link to this function

# set_tenant(changeset, tenant)

View Source

    @spec set_tenant(t(), String.t()) :: t()

Link to this function

# timeout(changeset, timeout, default \\ nil)

View Source

    @spec timeout(t(), nil | pos_integer(), nil | pos_integer()) :: t()

Link to this function

# with_hooks(changeset, func)

View Source

    @spec with_hooks(
      t(),
      (t() ->
         {:ok, term(), %{notifications: [Ash.Notifier.Notification.t()]}}
         | {:error, term()})
    ) ::
      {:ok, term(), t(), %{notifications: [Ash.Notifier.Notification.t()]}}
      | {:error, term()}

Wraps a function in the before/after action hooks of a changeset.

The function takes a changeset and if it returns `{:ok, result}`, the result will be passed through the after action hooks.