defmodule Ash.Resource.Attribute do
@moduledoc "Represents an attribute on a resource"
defstruct [
:name,
:type,
:allow_nil?,
:generated?,
:primary_key?,
:private?,
:writable?,
:always_select?,
:default,
:update_default,
:description,
:source,
match_other_defaults?: false,
sensitive?: false,
filterable?: true,
constraints: []
]
defmodule Helpers do
@moduledoc "Helpers for building attributes"
defmacro timestamps(opts \\ []) do
quote do
create_timestamp :inserted_at, unquote(opts)
update_timestamp :updated_at, unquote(opts)
end
end
end
@type t :: %__MODULE__{
name: atom(),
constraints: Keyword.t(),
type: Ash.Type.t(),
primary_key?: boolean(),
private?: boolean(),
default: nil | term | (-> term),
update_default: nil | term | (-> term) | (Ash.Resource.record() -> term),
sensitive?: boolean(),
writable?: boolean()
}
alias Spark.OptionsHelpers
@schema [
name: [
type: :atom,
doc: "The name of the attribute."
],
type: [
type: Ash.OptionsHelpers.ash_type(),
doc: "The type of the attribute. See `Ash.Type` for more."
],
constraints: [
type: :keyword_list,
doc: """
Constraints to provide to the type when casting the value.
For more information see the specific type's documentation,
for general type information see `Ash.Type` and
for practical example [see the constraints topic](/documentation/topics/constraints.md).
"""
],
description: [
type: :string,
doc: "An optional description for the attribute."
],
sensitive?: [
type: :boolean,
default: false,
doc: """
Whether or not the attribute value contains sensitive information, like PII.
Using this option will cause the attribute to be `** Redacted **` from the resource when logging or inspecting.
See the [Security guide](/documentation/topics/security.md) for more.
"""
],
source: [
type: :atom,
doc: """
If the field should be mapped to a different name in the data layer. Support varies by data layer.
"""
],
always_select?: [
type: :boolean,
default: false,
doc: """
Whether or not to ensure this attribute is always selected when reading from the database.
When this option is true and performing a read action, the attribute will **always** be selected even if it was explicitly selected out of the query.
For example say there is a resource with two attributes `:foo` and `:bar`.
Say `:foo` has `always_select? true` set.
The query `Ash.Query.select(MyResource, [:bar])` would return both `:foo` and `:bar` even though `:foo` was not selected in the query.
"""
],
primary_key?: [
type: :boolean,
default: false,
doc: """
Whether the attribute is the primary key.
Composite primary key is also possible by using `primary_key? true` in more than one attribute.
If primary_key? is true, allow_nil? must be false.
"""
],
allow_nil?: [
type: :boolean,
default: true,
doc: """
Whether or not the attribute can be set to nil.
If nil value is given error is raised.
"""
],
generated?: [
type: :boolean,
default: false,
doc: """
Whether or not the value may be generated by the data layer.
"""
],
writable?: [
type: :boolean,
default: true,
doc: """
Whether or not the value can be written to.
If `writable? false` then attribute is read-only and cannot be written to even when creating a record.
This can be overridden with `Ash.Changeset.force_change_attribute/3`.
"""
],
private?: [
type: :boolean,
default: false,
doc: """
If `private? true` then attribute is read-only and cannot be written to even when creating a record.
Additionally it tells other extensions (e.g. AshJsonApi or AshGraphql) not to expose these attributes through the API.
The value of the attribute can be overridden with `Ash.Changeset.force_change_attribute/3`.
See the [security guide](/documentation/topics/security.md) for more.
"""
],
default: [
type: {:or, [{:mfa_or_fun, 0}, :literal]},
doc: "A value to be set on all creates, unless a value is being provided already."
],
update_default: [
type: {:or, [{:mfa_or_fun, 0}, :literal]},
doc: "A value to be set on all updates, unless a value is being provided already."
],
filterable?: [
type: {:or, [:boolean, {:in, [:simple_equality]}]},
default: true,
doc: """
Whether or not the attribute can be referenced in filters.
Can be used to prevent filtering on large text columns with no indexing.
"""
],
match_other_defaults?: [
type: :boolean,
default: false,
doc: """
Ensures that other attributes that use the same "lazy" default (a function or an mfa), use the same default value.
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
get the same value, instead of having slightly different timestamps.
"""
]
]
@create_timestamp_schema @schema
|> OptionsHelpers.set_default!(:writable?, false)
|> OptionsHelpers.set_default!(:private?, true)
|> OptionsHelpers.set_default!(:default, &DateTime.utc_now/0)
|> OptionsHelpers.set_default!(:match_other_defaults?, true)
|> OptionsHelpers.set_default!(:type, Ash.Type.UtcDatetimeUsec)
|> OptionsHelpers.set_default!(:allow_nil?, false)
|> Ash.OptionsHelpers.hide_all_except([:name])
@update_timestamp_schema @schema
|> OptionsHelpers.set_default!(:writable?, false)
|> OptionsHelpers.set_default!(:private?, true)
|> OptionsHelpers.set_default!(:match_other_defaults?, true)
|> OptionsHelpers.set_default!(:default, &DateTime.utc_now/0)
|> OptionsHelpers.set_default!(
:update_default,
&DateTime.utc_now/0
)
|> OptionsHelpers.set_default!(:type, Ash.Type.UtcDatetimeUsec)
|> OptionsHelpers.set_default!(:allow_nil?, false)
|> Ash.OptionsHelpers.hide_all_except([:name])
@uuid_primary_key_schema @schema
|> OptionsHelpers.set_default!(:writable?, false)
|> OptionsHelpers.set_default!(:default, &Ash.UUID.generate/0)
|> OptionsHelpers.set_default!(:primary_key?, true)
|> OptionsHelpers.set_default!(:type, :uuid)
|> Keyword.delete(:allow_nil?)
|> Ash.OptionsHelpers.hide_all_except([:name])
@integer_primary_key_schema @schema
|> OptionsHelpers.set_default!(:writable?, false)
|> OptionsHelpers.set_default!(:primary_key?, true)
|> OptionsHelpers.set_default!(:generated?, true)
|> OptionsHelpers.set_default!(:type, :integer)
|> Keyword.delete(:allow_nil?)
|> Ash.OptionsHelpers.hide_all_except([:name])
def transform(attribute) do
Ash.Type.set_type_transformation(%{attribute | source: attribute.source || attribute.name})
end
@doc false
def attribute_schema, do: @schema
def create_timestamp_schema, do: @create_timestamp_schema
def update_timestamp_schema, do: @update_timestamp_schema
def uuid_primary_key_schema, do: @uuid_primary_key_schema
def integer_primary_key_schema, do: @integer_primary_key_schema
end