docs: more DSL docs, create shell guides

This commit is contained in:
Zach Daniel 2022-08-14 12:49:21 -04:00
parent 6a7f36a122
commit 73124c43fd
17 changed files with 349 additions and 132 deletions

View file

@ -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(

View file

@ -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
```
""" """
] ]
] ]

View file

@ -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"]
]
] ]
] ]

View file

@ -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."
] ]

View file

@ -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.

View file

@ -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()

View file

@ -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,

View file

@ -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.
""" """
] ]
], ],

View file

@ -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"
] ]

View file

@ -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

View file

@ -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"
] ]
] ]

View 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.

View 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
```