mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 13:33:20 +12:00
docs: more DSL docs, create shell guides
This commit is contained in:
parent
6a7f36a122
commit
73124c43fd
17 changed files with 349 additions and 132 deletions
|
@ -39,8 +39,13 @@ defmodule Ash.Resource.Actions.Create do
|
||||||
type: {:list, :atom},
|
type: {:list, :atom},
|
||||||
doc: """
|
doc: """
|
||||||
A list of attributes that would normally be required, but should not be for this action. They will still be validated just before
|
A list of attributes that would normally be required, but should not be for this action. They will still be validated just before
|
||||||
the record is created, but this allows for setting required attributes in your changes under the hood.
|
the record is created.
|
||||||
"""
|
""",
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Actions"
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|> Ash.OptionsHelpers.merge_schemas(
|
|> Ash.OptionsHelpers.merge_schemas(
|
||||||
|
|
|
@ -5,16 +5,21 @@ defmodule Ash.Resource.Actions.SharedOptions do
|
||||||
name: [
|
name: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
required: true,
|
required: true,
|
||||||
doc: "The name of the action"
|
doc: "The name of the action",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
primary?: [
|
primary?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
doc: "Whether or not this action should be used when no action is specified by the caller."
|
doc: "Whether or not this action should be used when no action is specified by the caller.",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
description: [
|
description: [
|
||||||
type: :string,
|
type: :string,
|
||||||
doc: "An optional description for the action"
|
doc: "An optional description for the action",
|
||||||
|
links: [
|
||||||
|
"ash:guide:Documentation"
|
||||||
|
]
|
||||||
],
|
],
|
||||||
transaction?: [
|
transaction?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
@ -22,24 +27,27 @@ defmodule Ash.Resource.Actions.SharedOptions do
|
||||||
Whether or not the action should be run in transactions. Reads default to false, while create/update/destroy actions default to `true`.
|
Whether or not the action should be run in transactions. Reads default to false, while create/update/destroy actions default to `true`.
|
||||||
|
|
||||||
Has no effect if the data layer does not support transactions, or if that data layer is already in a transaction.
|
Has no effect if the data layer does not support transactions, or if that data layer is already in a transaction.
|
||||||
"""
|
""",
|
||||||
|
links: [
|
||||||
|
"ash:guide:Transactions"
|
||||||
|
]
|
||||||
],
|
],
|
||||||
touches_resources: [
|
touches_resources: [
|
||||||
type: {:list, :atom},
|
type: {:list, :atom},
|
||||||
doc: """
|
doc: """
|
||||||
A list of resources that the action may touch.
|
A list of resources that the action may touch, used when building transactions.
|
||||||
|
""",
|
||||||
If your action has custom code (i.e custom changes) that touch resources that use a different data layer,
|
links: [
|
||||||
this can be used to inform the transaction logic that these resource's data layer's should be involved in the
|
"ash:guide:Transactions"
|
||||||
transaction. In most standard set ups, this should not be necessary.
|
]
|
||||||
"""
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@create_update_opts [
|
@create_update_opts [
|
||||||
accept: [
|
accept: [
|
||||||
type: {:or, [in: [:all], list: :atom]},
|
type: {:or, [in: [:all], list: :atom]},
|
||||||
doc: "The list of attributes to accept. Defaults to all attributes on the resource"
|
doc: "The list of attributes to accept. Defaults to all attributes on the resource",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
reject: [
|
reject: [
|
||||||
type: {:or, [in: [:all], list: :atom]},
|
type: {:or, [in: [:all], list: :atom]},
|
||||||
|
@ -48,59 +56,32 @@ defmodule Ash.Resource.Actions.SharedOptions do
|
||||||
|
|
||||||
If this is specified along with `accept`, then everything in the `accept` list minus any matches in the
|
If this is specified along with `accept`, then everything in the `accept` list minus any matches in the
|
||||||
`reject` list will be accepted.
|
`reject` list will be accepted.
|
||||||
"""
|
""",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
require_attributes: [
|
require_attributes: [
|
||||||
type: {:list, :atom},
|
type: {:list, :atom},
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
A list of attributes that would normally `allow_nil` to require for this action.
|
A list of attributes that would normally `allow_nil?`, to require for this action.
|
||||||
|
|
||||||
No need to include attributes that are `allow_nil?: false`.
|
No need to include attributes that already do not allow nil?
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
error_handler: [
|
error_handler: [
|
||||||
type: :mfa,
|
type: :mfa,
|
||||||
|
links: [],
|
||||||
doc: "Sets the error handler on the changeset. See `Ash.Changeset.handle_errors/2` for more"
|
doc: "Sets the error handler on the changeset. See `Ash.Changeset.handle_errors/2` for more"
|
||||||
],
|
],
|
||||||
manual?: [
|
manual?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Manual Actions"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc: """
|
doc: """
|
||||||
Instructs Ash to *skip* the actual update/create/destroy step.
|
Instructs Ash to *skip* the actual update/create/destroy step at the data layer. See the manual action guides for more.
|
||||||
|
|
||||||
All validation still takes place, but the `result` in any `after_action` callbacks
|
|
||||||
attached to that action will simply be the record that was read from the database initially.
|
|
||||||
For creates, the `result` will be `nil`, and you will be expected to handle the changeset in
|
|
||||||
an after_action callback and return an instance of the record. This is a good way to prevent
|
|
||||||
Ash from issuing an unnecessary update to the record, e.g updating the `updated_at` of the record
|
|
||||||
when an action actually only involves modifying relating records.
|
|
||||||
|
|
||||||
You could then handle the changeset automatically.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
# in the action
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
action :special_create do
|
|
||||||
manual? true
|
|
||||||
change MyApp.DoCreate
|
|
||||||
end
|
|
||||||
|
|
||||||
# The change
|
|
||||||
defmodule MyApp.DoCreate do
|
|
||||||
use Ash.Resource.Change
|
|
||||||
|
|
||||||
def change(changeset, _, _) do
|
|
||||||
Ash.Changeset.after_action(changeset, fn changeset, _result ->
|
|
||||||
# result will be `nil`, because this is a manual action
|
|
||||||
|
|
||||||
result = do_something_that_creates_the_record(changeset)
|
|
||||||
|
|
||||||
{:ok, result}
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -55,6 +55,13 @@ defmodule Ash.Resource.Attribute do
|
||||||
modules: ["ash:module:Ash.Type"]
|
modules: ["ash:module:Ash.Type"]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
description: [
|
||||||
|
type: :string,
|
||||||
|
doc: "An optional description for the attribute.",
|
||||||
|
links: [
|
||||||
|
modules: ["ash:guide:Documentation"]
|
||||||
|
]
|
||||||
|
],
|
||||||
sensitive?: [
|
sensitive?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -97,7 +104,9 @@ defmodule Ash.Resource.Attribute do
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
doc: "Whether or not the value may be generated by the data layer.",
|
doc: "Whether or not the value may be generated by the data layer.",
|
||||||
links: []
|
links: [
|
||||||
|
guides: ["ash:guide:Actions"]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
writable?: [
|
writable?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
@ -114,22 +123,25 @@ defmodule Ash.Resource.Attribute do
|
||||||
guides: ["ash:guide:Security"]
|
guides: ["ash:guide:Security"]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
default: [
|
||||||
|
type: {:or, [{:mfa_or_fun, 0}, :literal]},
|
||||||
|
doc: "A value to be set on all creates, unless a value is being provided already.",
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Actions"]
|
||||||
|
]
|
||||||
|
],
|
||||||
update_default: [
|
update_default: [
|
||||||
type: {:or, [{:mfa_or_fun, 0}, :literal]},
|
type: {:or, [{:mfa_or_fun, 0}, :literal]},
|
||||||
doc: "A value to be set on all updates, unless a value is being provided already."
|
doc: "A value to be set on all updates, unless a value is being provided already.",
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Actions"]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
filterable?: [
|
filterable?: [
|
||||||
type: {:or, [:boolean, {:in, [:simple_equality]}]},
|
type: {:or, [:boolean, {:in, [:simple_equality]}]},
|
||||||
default: true,
|
default: true,
|
||||||
doc: "Whether or not the attribute can be referenced in filters."
|
doc: "Whether or not the attribute can be referenced in filters.",
|
||||||
],
|
links: []
|
||||||
default: [
|
|
||||||
type: {:or, [{:mfa_or_fun, 0}, :literal]},
|
|
||||||
doc: "A value to be set on all creates, unless a value is being provided already."
|
|
||||||
],
|
|
||||||
description: [
|
|
||||||
type: :string,
|
|
||||||
doc: "An optional description for the attribute."
|
|
||||||
],
|
],
|
||||||
match_other_defaults?: [
|
match_other_defaults?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
@ -139,7 +151,10 @@ defmodule Ash.Resource.Attribute do
|
||||||
Has no effect unless `default` is a zero argument function.
|
Has no effect unless `default` is a zero argument function.
|
||||||
For example, create and update timestamps use this option, and have the same lazy function `&DateTime.utc_now/0`, so they
|
For example, create and update timestamps use this option, and have the same lazy function `&DateTime.utc_now/0`, so they
|
||||||
get the same value, instead of having slightly different timestamps.
|
get the same value, instead of having slightly different timestamps.
|
||||||
"""
|
""",
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Actions"]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -19,64 +19,88 @@ defmodule Ash.Resource.Calculation do
|
||||||
name: [
|
name: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
required: true,
|
required: true,
|
||||||
|
links: [],
|
||||||
doc: "The field name to use for the calculation value"
|
doc: "The field name to use for the calculation value"
|
||||||
],
|
],
|
||||||
type: [
|
type: [
|
||||||
type: :any,
|
type: :any,
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Type"
|
||||||
|
]
|
||||||
|
],
|
||||||
required: true
|
required: true
|
||||||
],
|
],
|
||||||
constraints: [
|
constraints: [
|
||||||
type: :keyword_list,
|
type: :keyword_list,
|
||||||
default: [],
|
default: [],
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Type"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc: "Constraints to provide to the type."
|
doc: "Constraints to provide to the type."
|
||||||
],
|
],
|
||||||
allow_async?: [
|
allow_async?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Actions"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc: """
|
doc: """
|
||||||
If set to `true`, then the calculation may be run after the main query.
|
If set to `true`, then the calculation may be run after the main query.
|
||||||
|
|
||||||
This is useful for calculations that are very expensive, especially when combined with complex filters/join
|
|
||||||
scenarios. By adding this, we will rerun a trimmed down version of the main query, using the primary keys for
|
|
||||||
fast access. This will be done asynchronously for each calculation that has `allow_async?: true`.
|
|
||||||
|
|
||||||
Keep in mind that if the calculation is used in a filter or sort, it cannot be done asynchrnously,
|
|
||||||
and *must* be done in the main query.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
calculation: [
|
calculation: [
|
||||||
type: {:custom, __MODULE__, :calculation, []},
|
type: {:custom, __MODULE__, :calculation, []},
|
||||||
required: true,
|
required: true,
|
||||||
|
links: [],
|
||||||
doc: "The module or `{module, opts}` to use for the calculation"
|
doc: "The module or `{module, opts}` to use for the calculation"
|
||||||
],
|
],
|
||||||
description: [
|
description: [
|
||||||
type: :string,
|
type: :string,
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Documentation"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc: "An optional description for the calculation"
|
doc: "An optional description for the calculation"
|
||||||
],
|
],
|
||||||
private?: [
|
private?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Security"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"Whether or not the calculation will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
|
"Whether or not the calculation will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
|
||||||
],
|
],
|
||||||
select: [
|
select: [
|
||||||
type: {:list, :atom},
|
type: {:list, :atom},
|
||||||
default: [],
|
default: [],
|
||||||
doc: "A list of fields to ensure selected in the case that the calculation is run."
|
links: [],
|
||||||
|
doc: "A list of fields to ensure selected if the calculation is used."
|
||||||
],
|
],
|
||||||
load: [
|
load: [
|
||||||
type: :any,
|
type: :any,
|
||||||
default: [],
|
default: [],
|
||||||
|
links: [],
|
||||||
doc: "A load statement to be applied if the calculation is used."
|
doc: "A load statement to be applied if the calculation is used."
|
||||||
],
|
],
|
||||||
allow_nil?: [
|
allow_nil?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
links: [],
|
||||||
doc: "Whether or not the calculation can return nil."
|
doc: "Whether or not the calculation can return nil."
|
||||||
],
|
],
|
||||||
filterable?: [
|
filterable?: [
|
||||||
type: {:or, [:boolean, {:in, [:simple_equality]}]},
|
type: {:or, [:boolean, {:in, [:simple_equality]}]},
|
||||||
default: true,
|
default: true,
|
||||||
|
links: [],
|
||||||
doc: "Whether or not the calculation should be usable in filters."
|
doc: "Whether or not the calculation should be usable in filters."
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -98,26 +122,39 @@ defmodule Ash.Resource.Calculation do
|
||||||
name: [
|
name: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
required: true,
|
required: true,
|
||||||
doc: "The name to use for the argument"
|
doc: "The name to use for the argument",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
type: [
|
type: [
|
||||||
type: :ash_type,
|
type: :ash_type,
|
||||||
required: true,
|
required: true,
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Type"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc: "The type of the argument"
|
doc: "The type of the argument"
|
||||||
],
|
],
|
||||||
default: [
|
default: [
|
||||||
type: {:or, [{:mfa_or_fun, 0}, :literal]},
|
type: {:or, [{:mfa_or_fun, 0}, :literal]},
|
||||||
required: false,
|
required: false,
|
||||||
|
links: [],
|
||||||
doc: "A default value to use for the argument if not provided"
|
doc: "A default value to use for the argument if not provided"
|
||||||
],
|
],
|
||||||
allow_nil?: [
|
allow_nil?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: true,
|
default: true,
|
||||||
doc: "Whether or not the argument value may be nil"
|
links: [],
|
||||||
|
doc: "Whether or not the argument value may be nil (or may be not provided)"
|
||||||
],
|
],
|
||||||
constraints: [
|
constraints: [
|
||||||
type: :keyword_list,
|
type: :keyword_list,
|
||||||
default: [],
|
default: [],
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Type"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"Constraints to provide to the type when casting the value. See the type's documentation for more information."
|
"Constraints to provide to the type when casting the value. See the type's documentation for more information."
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Ash.Resource.Change do
|
||||||
when this change was configured on a resource, and the context, which currently only has
|
when this change was configured on a resource, and the context, which currently only has
|
||||||
the actor.
|
the actor.
|
||||||
"""
|
"""
|
||||||
defstruct [:change, :on, :only_when_valid?, where: []]
|
defstruct [:change, :on, :only_when_valid?, :description, where: []]
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
@ -21,22 +21,30 @@ defmodule Ash.Resource.Change do
|
||||||
type: {:custom, __MODULE__, :on, []},
|
type: {:custom, __MODULE__, :on, []},
|
||||||
default: [:create, :update],
|
default: [:create, :update],
|
||||||
doc: """
|
doc: """
|
||||||
The action types the validation should run on.
|
The action types the validation should run on. Destroy actions are omitted by default as most changes don't make sense for a destroy.
|
||||||
|
""",
|
||||||
Many validations don't make sense in the context of deletion, so by default it is left out of the list.
|
links: []
|
||||||
"""
|
|
||||||
],
|
],
|
||||||
only_when_valid?: [
|
only_when_valid?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
If the change should only be run on valid changes. By default, all changes are run unless stated otherwise here.
|
If the change should only be run on valid changes. By default, all changes are run unless stated otherwise here.
|
||||||
|
|
||||||
For 2.0 this may become the default.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
|
description: [
|
||||||
|
type: :string,
|
||||||
|
doc: "An optional description for the change",
|
||||||
|
links: []
|
||||||
|
],
|
||||||
change: [
|
change: [
|
||||||
type: {:ash_behaviour, Ash.Resource.Change, Ash.Resource.Change.Builtins},
|
type: {:ash_behaviour, Ash.Resource.Change, Ash.Resource.Change.Builtins},
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Resource.Change.Builtins"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc: """
|
doc: """
|
||||||
The module and options for a change.
|
The module and options for a change.
|
||||||
""",
|
""",
|
||||||
|
@ -46,6 +54,11 @@ defmodule Ash.Resource.Change do
|
||||||
type:
|
type:
|
||||||
{:list, {:ash_behaviour, Ash.Resource.Validation, Ash.Resource.Validation.Builtins}},
|
{:list, {:ash_behaviour, Ash.Resource.Validation, Ash.Resource.Validation.Builtins}},
|
||||||
required: false,
|
required: false,
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Resource.Validation.Builtins"
|
||||||
|
]
|
||||||
|
],
|
||||||
default: [],
|
default: [],
|
||||||
doc: """
|
doc: """
|
||||||
Validations that should pass in order for this validation to apply.
|
Validations that should pass in order for this validation to apply.
|
||||||
|
|
|
@ -160,6 +160,11 @@ defmodule Ash.Resource.Dsl do
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Relationships"
|
||||||
|
]
|
||||||
|
],
|
||||||
modules: [:destination],
|
modules: [:destination],
|
||||||
target: Ash.Resource.Relationships.HasOne,
|
target: Ash.Resource.Relationships.HasOne,
|
||||||
schema: Ash.Resource.Relationships.HasOne.opt_schema(),
|
schema: Ash.Resource.Relationships.HasOne.opt_schema(),
|
||||||
|
@ -180,6 +185,11 @@ defmodule Ash.Resource.Dsl do
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Relationships"
|
||||||
|
]
|
||||||
|
],
|
||||||
target: Ash.Resource.Relationships.HasMany,
|
target: Ash.Resource.Relationships.HasMany,
|
||||||
modules: [:destination],
|
modules: [:destination],
|
||||||
schema: Ash.Resource.Relationships.HasMany.opt_schema(),
|
schema: Ash.Resource.Relationships.HasMany.opt_schema(),
|
||||||
|
@ -193,6 +203,11 @@ defmodule Ash.Resource.Dsl do
|
||||||
|
|
||||||
A join table is typically a table who's primary key consists of one foreign key to each resource.
|
A join table is typically a table who's primary key consists of one foreign key to each resource.
|
||||||
""",
|
""",
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Relationships"
|
||||||
|
]
|
||||||
|
],
|
||||||
examples: [
|
examples: [
|
||||||
"""
|
"""
|
||||||
# In a resource called `Word`
|
# In a resource called `Word`
|
||||||
|
@ -223,6 +238,11 @@ defmodule Ash.Resource.Dsl do
|
||||||
|
|
||||||
This creates a field on the resource with the corresponding name and type, unless `define_field?: false` is provided.
|
This creates a field on the resource with the corresponding name and type, unless `define_field?: false` is provided.
|
||||||
""",
|
""",
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Relationships"
|
||||||
|
]
|
||||||
|
],
|
||||||
examples: [
|
examples: [
|
||||||
"""
|
"""
|
||||||
# In a resource called `Word`
|
# In a resource called `Word`
|
||||||
|
@ -246,6 +266,11 @@ defmodule Ash.Resource.Dsl do
|
||||||
Relationships are a core component of resource oriented design. Many components of Ash
|
Relationships are a core component of resource oriented design. Many components of Ash
|
||||||
will use these relationships. A simple use case is loading relationships (done via the `Ash.Query.load/2`).
|
will use these relationships. A simple use case is loading relationships (done via the `Ash.Query.load/2`).
|
||||||
""",
|
""",
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Relationships"
|
||||||
|
]
|
||||||
|
],
|
||||||
examples: [
|
examples: [
|
||||||
"""
|
"""
|
||||||
relationships do
|
relationships do
|
||||||
|
@ -348,6 +373,14 @@ defmodule Ash.Resource.Dsl do
|
||||||
|
|
||||||
@change %Ash.Dsl.Entity{
|
@change %Ash.Dsl.Entity{
|
||||||
name: :change,
|
name: :change,
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Actions"
|
||||||
|
],
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Resource.Change"
|
||||||
|
]
|
||||||
|
],
|
||||||
describe: """
|
describe: """
|
||||||
A change to be applied to the changeset after it is generated. They are run in order, from top to bottom.
|
A change to be applied to the changeset after it is generated. They are run in order, from top to bottom.
|
||||||
|
|
||||||
|
@ -376,6 +409,14 @@ defmodule Ash.Resource.Dsl do
|
||||||
"validate {Mod, [foo: :bar]}",
|
"validate {Mod, [foo: :bar]}",
|
||||||
"validate at_least_one_of_present([:first_name, :last_name])"
|
"validate at_least_one_of_present([:first_name, :last_name])"
|
||||||
],
|
],
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Actions"
|
||||||
|
],
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Resource.Change"
|
||||||
|
]
|
||||||
|
],
|
||||||
target: Ash.Resource.Validation,
|
target: Ash.Resource.Validation,
|
||||||
schema: Ash.Resource.Validation.opt_schema(),
|
schema: Ash.Resource.Validation.opt_schema(),
|
||||||
no_depend_modules: [:validation],
|
no_depend_modules: [:validation],
|
||||||
|
@ -548,6 +589,11 @@ defmodule Ash.Resource.Dsl do
|
||||||
Ash.Resource.Validation.Builtins,
|
Ash.Resource.Validation.Builtins,
|
||||||
Ash.Filter.TemplateHelpers
|
Ash.Filter.TemplateHelpers
|
||||||
],
|
],
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Actions"
|
||||||
|
]
|
||||||
|
],
|
||||||
schema: [
|
schema: [
|
||||||
defaults: [
|
defaults: [
|
||||||
type: {:list, {:in, [:create, :read, :update, :destroy]}},
|
type: {:list, {:in, [:create, :read, :update, :destroy]}},
|
||||||
|
@ -558,10 +604,14 @@ defmodule Ash.Resource.Dsl do
|
||||||
|
|
||||||
By default, resources have no default actions. Embedded resources, however, have a default
|
By default, resources have no default actions. Embedded resources, however, have a default
|
||||||
of all resource types.
|
of all resource types.
|
||||||
"""
|
""",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
default_accept: [
|
default_accept: [
|
||||||
type: {:list, :atom}
|
type: {:list, :atom},
|
||||||
|
doc:
|
||||||
|
"A default value for the `accept` option for each action. Defaults to all public attributes.",
|
||||||
|
links: []
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
examples: [
|
examples: [
|
||||||
|
@ -745,6 +795,9 @@ defmodule Ash.Resource.Dsl do
|
||||||
describe: """
|
describe: """
|
||||||
Declare validations prior to performing actions against the resource
|
Declare validations prior to performing actions against the resource
|
||||||
""",
|
""",
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guides:Actions"]
|
||||||
|
],
|
||||||
imports: [Ash.Resource.Validation.Builtins],
|
imports: [Ash.Resource.Validation.Builtins],
|
||||||
examples: [
|
examples: [
|
||||||
"""
|
"""
|
||||||
|
@ -923,6 +976,11 @@ defmodule Ash.Resource.Dsl do
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Calculations"
|
||||||
|
]
|
||||||
|
],
|
||||||
target: Ash.Resource.Calculation.Argument,
|
target: Ash.Resource.Calculation.Argument,
|
||||||
args: [:name, :type],
|
args: [:name, :type],
|
||||||
schema: Ash.Resource.Calculation.Argument.schema()
|
schema: Ash.Resource.Calculation.Argument.schema()
|
||||||
|
|
|
@ -61,34 +61,51 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
||||||
[
|
[
|
||||||
primary_key?: [
|
primary_key?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Attributes"]
|
||||||
|
],
|
||||||
default: false,
|
default: false,
|
||||||
doc: "Whether this field is, or is part of, the primary key of a resource."
|
doc:
|
||||||
|
"Whether the generated attribute is, or is part of, the primary key of a resource."
|
||||||
],
|
],
|
||||||
required?: [
|
required?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Attributes"]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"Whether this relationship must always be present, e.g: must be included on creation, and never removed (it can still be changed)"
|
"Whether this relationship must always be present, e.g: must be included on creation, and never removed (it may be modified). The generated attribute will not allow nil values."
|
||||||
],
|
],
|
||||||
attribute_writable?: [
|
attribute_writable?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Attributes"]
|
||||||
|
],
|
||||||
doc: """
|
doc: """
|
||||||
Whether this relationship's generated attribute will be marked as public & writable.
|
Whether the generated attribute will be marked as public & writable.
|
||||||
|
|
||||||
Has no effect when combined with `define_field?: false`.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
define_field?: [
|
define_field?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Attributes"]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"If set to `false` a field is not created on the resource for this relationship, and one must be manually added in `attributes`."
|
"If set to `false` an attribute is not created on the resource for this relationship, and one must be manually added in `attributes`, invalidating many other options."
|
||||||
],
|
],
|
||||||
field_type: [
|
field_type: [
|
||||||
type: :any,
|
type: :any,
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Type"
|
||||||
|
],
|
||||||
|
guides: ["ash:guide:Attributes"]
|
||||||
|
],
|
||||||
default: :uuid,
|
default: :uuid,
|
||||||
doc: "The field type of the automatically created field."
|
doc: "The type of the generated created attribute."
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@global_opts,
|
@global_opts,
|
||||||
|
|
|
@ -57,10 +57,9 @@ defmodule Ash.Resource.Relationships.HasOne do
|
||||||
[
|
[
|
||||||
required?: [
|
required?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
Marks the relationship as required. This is *not* currently validated anywhere, since the
|
Marks the relationship as required. Has no effect on validations, but can inform extensions that there will always be a related entity.
|
||||||
relationship is managed by the destination, but ash_graphql uses it for type information,
|
|
||||||
and it can be used for expressiveness.
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
|
@ -57,22 +57,34 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
||||||
source_field_on_join_table: [
|
source_field_on_join_table: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
required: true,
|
required: true,
|
||||||
|
links: [
|
||||||
|
dsls: [
|
||||||
|
"ash:dsl:attributes/attribute"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"The field on the join table that should line up with `source_field` on this resource."
|
"The attribute on the join resource that should line up with `source_field` on this resource."
|
||||||
],
|
],
|
||||||
destination_field_on_join_table: [
|
destination_field_on_join_table: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
required: true,
|
required: true,
|
||||||
|
links: [
|
||||||
|
dsls: [
|
||||||
|
"ash:dsl:attributes/attribute"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"The field on the join table that should line up with `destination_field` on the related resource."
|
"The attribute on the join resource that should line up with `destination_field` on the related resource."
|
||||||
],
|
],
|
||||||
through: [
|
through: [
|
||||||
type: :ash_resource,
|
type: :ash_resource,
|
||||||
required: true,
|
required: true,
|
||||||
|
links: [],
|
||||||
doc: "The resource to use as the join resource."
|
doc: "The resource to use as the join resource."
|
||||||
],
|
],
|
||||||
join_relationship: [
|
join_relationship: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
|
links: [],
|
||||||
doc:
|
doc:
|
||||||
"The has_many relationship to the join table. Defaults to <relationship_name>_join_assoc"
|
"The has_many relationship to the join table. Defaults to <relationship_name>_join_assoc"
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,102 +4,125 @@ defmodule Ash.Resource.Relationships.SharedOptions do
|
||||||
@shared_options [
|
@shared_options [
|
||||||
name: [
|
name: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: "The name of the relationship"
|
doc: "The name of the relationship",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
destination: [
|
destination: [
|
||||||
type: :ash_resource,
|
type: :ash_resource,
|
||||||
doc: "The destination resource"
|
doc: "The destination resource",
|
||||||
|
links: []
|
||||||
|
],
|
||||||
|
description: [
|
||||||
|
type: :string,
|
||||||
|
doc: "An optional description for the relationship",
|
||||||
|
links: [
|
||||||
|
modules: ["ash:guide:Documentation"]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
destination_field: [
|
destination_field: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
|
links: [
|
||||||
|
dsls: [
|
||||||
|
"ash:dsl:attributes/attribute"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"The field on the related resource that should match the `source_field` on this resource."
|
"The attribute on the related resource that should match the `source_field` configured on this resource."
|
||||||
],
|
],
|
||||||
validate_destination_field?: [
|
validate_destination_field?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: true,
|
default: true,
|
||||||
doc:
|
doc:
|
||||||
"Whether or not to validate that the destination field exists on the destination resource"
|
"Whether or not to validate that the destination field exists on the destination resource",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
source_field: [
|
source_field: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
|
links: [
|
||||||
|
dsls: [
|
||||||
|
"ash:dsl:attributes/attribute"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"The field on this resource that should match the `destination_field` on the related resource."
|
"The field on this resource that should match the `destination_field` on the related resource."
|
||||||
],
|
],
|
||||||
description: [
|
|
||||||
type: :string,
|
|
||||||
doc: "An optional description for the relationship"
|
|
||||||
],
|
|
||||||
relationship_context: [
|
relationship_context: [
|
||||||
type: :any,
|
type: :any,
|
||||||
as: :context,
|
as: :context,
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
Context to be set on any queries or changesets generated for this relationship.
|
Context to be set on any queries or changesets generated for managing or querying this relationship.
|
||||||
|
|
||||||
This is used by ash_postgres for polymorphic resources.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
private?: [
|
private?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [
|
||||||
|
guides: ["ash:guide:Security"]
|
||||||
|
],
|
||||||
doc:
|
doc:
|
||||||
"Whether or not the relationship will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
|
"Whether or not the relationship will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
|
||||||
],
|
],
|
||||||
not_found_message: [
|
not_found_message: [
|
||||||
type: :string,
|
type: :string,
|
||||||
doc: """
|
doc: """
|
||||||
A message to show if there is a conflict with this relationship in the database on update or create.
|
A message to show if there is a conflict with this relationship in the database on update or create, or when managing relationships.
|
||||||
|
""",
|
||||||
For example, if a value is added that has no match in the destination (very hard to do with the way Ash relationship changes work).
|
links: [
|
||||||
"""
|
guides: ["ash:guide:Managing Relationships"]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
writable?: [
|
writable?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: true,
|
default: true,
|
||||||
doc: """
|
doc: """
|
||||||
Wether or not the relationship may be edited.
|
Wether or not the relationship may be managed.
|
||||||
"""
|
""",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
read_action: [
|
read_action: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
The read action on the destination resource to use when loading data.
|
The read action on the destination resource to use when loading data and filtering.
|
||||||
|
""",
|
||||||
Keep in mind, any filters that exist on the destination action are not honored when filtering on this
|
links: []
|
||||||
relationship. The only time the read action comes into play is when loading the actual relationship, which happens when they are loaded
|
|
||||||
explicitly and when the relationship is managed.
|
|
||||||
"""
|
|
||||||
],
|
],
|
||||||
api: [
|
api: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
The API module to use when working with the related entity.
|
The API module to use when working with the related entity.
|
||||||
"""
|
""",
|
||||||
|
links: [
|
||||||
|
guides: [
|
||||||
|
"ash:guide:Multiple Apis"
|
||||||
|
]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
filter: [
|
filter: [
|
||||||
type: :any,
|
type: :any,
|
||||||
doc: """
|
doc: """
|
||||||
A filter to be applied when reading the relationship.
|
A filter to be applied when reading the relationship.
|
||||||
"""
|
""",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
sort: [
|
sort: [
|
||||||
type: :any,
|
type: :any,
|
||||||
doc: """
|
doc: """
|
||||||
A sort statement to be applied when reading the relationship.
|
A sort statement to be applied when loading the relationship.
|
||||||
"""
|
""",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
could_be_related_at_creation?: [
|
could_be_related_at_creation?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
This toggle modifies the management of the relationship. Typically, on creation,
|
Wether or not related values may exist for this relationship at creation.
|
||||||
the existing value of the relationship isn't loaded. However, if it is possible for things
|
|
||||||
to be related *before* this record is created, for example if your data layers does not support
|
|
||||||
primary keys, then you should set this to true.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
violation_message: [
|
violation_message: [
|
||||||
type: :string,
|
type: :string,
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
A message to show if there is a conflict with this relationship in the database on destroy.
|
A message to show if there is a conflict with this relationship in the database on destroy.
|
||||||
For example, if a record is deleted while related records still exist (and aren't configured to cascade deletes)
|
For example, if a record is deleted while related records still exist (and aren't configured to cascade deletes)
|
||||||
|
@ -115,6 +138,7 @@ defmodule Ash.Resource.Relationships.SharedOptions do
|
||||||
{:no_fields?,
|
{:no_fields?,
|
||||||
[
|
[
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
If true, all existing entities are considered related, i.e this relationship is not based on any fields, and `source_field` and
|
If true, all existing entities are considered related, i.e this relationship is not based on any fields, and `source_field` and
|
||||||
`destination_field` are ignored.
|
`destination_field` are ignored.
|
||||||
|
@ -140,6 +164,7 @@ defmodule Ash.Resource.Relationships.SharedOptions do
|
||||||
def manual do
|
def manual do
|
||||||
{:manual,
|
{:manual,
|
||||||
type: {:ash_behaviour, Ash.Resource.ManualRelationship},
|
type: {:ash_behaviour, Ash.Resource.ManualRelationship},
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
Allows for relationships that are fetched manually. WARNING: EXPERIMENTAL
|
Allows for relationships that are fetched manually. WARNING: EXPERIMENTAL
|
||||||
|
|
||||||
|
|
|
@ -69,20 +69,27 @@ defmodule Ash.Resource.Validation do
|
||||||
validation: [
|
validation: [
|
||||||
type: {:ash_behaviour, Ash.Resource.Validation, Ash.Resource.Validation.Builtins},
|
type: {:ash_behaviour, Ash.Resource.Validation, Ash.Resource.Validation.Builtins},
|
||||||
required: true,
|
required: true,
|
||||||
doc: "The module/opts pair of the validation"
|
doc: "The module/opts pair of the validation",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
where: [
|
where: [
|
||||||
type: {:list, {:ash_behaviour, Ash.Resource.Validation, Ash.Resource.Validation.Builtins}},
|
type: {:list, {:ash_behaviour, Ash.Resource.Validation, Ash.Resource.Validation.Builtins}},
|
||||||
required: false,
|
required: false,
|
||||||
default: [],
|
default: [],
|
||||||
|
links: [
|
||||||
|
modules: [
|
||||||
|
"ash:module:Ash.Resource.Validation.Builtins"
|
||||||
|
]
|
||||||
|
],
|
||||||
doc: """
|
doc: """
|
||||||
Validations that should pass in order for this validation to apply.
|
Validations that should pass in order for this validation to apply.
|
||||||
These validations failing will not invalidate the changes, but instead just result in this validation being ignored.
|
These validations failing will not invalidate the changes, but will instead result in this validation being ignored.
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
on: [
|
on: [
|
||||||
type: {:custom, __MODULE__, :on, []},
|
type: {:custom, __MODULE__, :on, []},
|
||||||
default: [:create, :update],
|
default: [:create, :update],
|
||||||
|
links: [],
|
||||||
doc: """
|
doc: """
|
||||||
The action types the validation should run on.
|
The action types the validation should run on.
|
||||||
|
|
||||||
|
@ -92,22 +99,25 @@ defmodule Ash.Resource.Validation do
|
||||||
expensive?: [
|
expensive?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
links: [],
|
||||||
doc:
|
doc:
|
||||||
"If a validation is expensive, it won't be run on invalid changes. All inexpensive validations are always run, to provide informative errors."
|
"If a validation is expensive, it won't be run on invalid changes. All inexpensive validations are always run, to provide informative errors."
|
||||||
],
|
],
|
||||||
message: [
|
message: [
|
||||||
type: :string,
|
type: :string,
|
||||||
doc: "If provided, overrides any message set by the validation error"
|
doc: "If provided, overrides any message set by the validation error",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
description: [
|
description: [
|
||||||
type: :string,
|
type: :string,
|
||||||
doc: "An optional description for the validation"
|
doc: "An optional description for the validation",
|
||||||
|
links: []
|
||||||
],
|
],
|
||||||
before_action?: [
|
before_action?: [
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
doc:
|
links: [],
|
||||||
"If set to `true`, the validation is not run when building changesets using `Ash.Changeset.for_*`. The validation will only ever be run once the action itself is called."
|
doc: "If set to `true`, the validation will be run in a before_action hook"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
9
priv/documentation/topics/calculations.md
Normal file
9
priv/documentation/topics/calculations.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
|
||||||
|
taken from `allow_async?`
|
||||||
|
This is useful for calculations that are very expensive, especially when combined with complex filters/join
|
||||||
|
scenarios. By adding this, we will rerun a trimmed down version of the main query, using the primary keys for
|
||||||
|
fast access. This will be done asynchronously for each calculation that has `allow_async?: true`.
|
||||||
|
|
||||||
|
Keep in mind that if the calculation is used in a filter or sort, it cannot be done asynchronously,
|
||||||
|
and *must* be done in the main query.
|
0
priv/documentation/topics/documentation.md
Normal file
0
priv/documentation/topics/documentation.md
Normal file
36
priv/documentation/topics/manual-actions.md
Normal file
36
priv/documentation/topics/manual-actions.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
|
||||||
|
All validation still takes place, but the `result` in any `after_action` callbacks
|
||||||
|
attached to that action will simply be the record that was read from the database initially.
|
||||||
|
For creates, the `result` will be `nil`, and you will be expected to handle the changeset in
|
||||||
|
an after_action callback and return an instance of the record. This is a good way to prevent
|
||||||
|
Ash from issuing an unnecessary update to the record, e.g updating the `updated_at` of the record
|
||||||
|
when an action actually only involves modifying relating records.
|
||||||
|
|
||||||
|
You could then handle the changeset automatically.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
# in the action
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
action :special_create do
|
||||||
|
manual? true
|
||||||
|
change MyApp.DoCreate
|
||||||
|
end
|
||||||
|
|
||||||
|
# The change
|
||||||
|
defmodule MyApp.DoCreate do
|
||||||
|
use Ash.Resource.Change
|
||||||
|
|
||||||
|
def change(changeset, _, _) do
|
||||||
|
Ash.Changeset.after_action(changeset, fn changeset, _result ->
|
||||||
|
# result will be `nil`, because this is a manual action
|
||||||
|
|
||||||
|
result = do_something_that_creates_the_record(changeset)
|
||||||
|
|
||||||
|
{:ok, result}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
0
priv/documentation/topics/multiple_apis.md
Normal file
0
priv/documentation/topics/multiple_apis.md
Normal file
0
priv/documentation/topics/relationships.md
Normal file
0
priv/documentation/topics/relationships.md
Normal file
0
priv/documentation/topics/transactions.md
Normal file
0
priv/documentation/topics/transactions.md
Normal file
Loading…
Reference in a new issue