I’m working on a feature that lets users add custom fields to records. For this, I have three resources:
FieldType with columns name and description.
Field with columns name, field_type_id, description, and options.
FieldResourceRecord with columns field_id, resource_name, resource_record_id, and value.
Any resource needing custom fields will have a one-to-many relationship with FieldResourceRecord where resource_name matches ResourceWithManyFields.
Currently, I’m defining the has_many :fields, MyApp.Fields.FieldResourceRecord relationship on each resource. Is there a way to centralize this relationship, similar to how we handle preparations, changes, and manual_actions?
You can centralize it by writing an extension, although for just one relationship it may be sort of a nuclear option 
defmodule YourApp.CustomFields do
use Spark.Dsl.Extension,
transformers: [YourApp.CustomFields.Transformers.AddCustomFields]
end
defmodule YourApp.CustomFields.Transformers.AddCustomFields do
use Spark.Dsl.Transformer
def transform(dsl) do
dsl
|> Ash.Resource.Builder.add_new_relationship(:has_many, :fields, MyApp>Fields.FieldResourceRecord)
end
end
Then you can do
use Ash.Resource, extensions: [YourApp.CustomFields]
1 Like
Thank you @zachdaniel, how can I add the following filters to that relationship in this extension. I could not determine how to do it in the documentation: Ash.Resource.Builder — ash v3.4.5
has_many :fields, Hr.Fields.FieldResourceRecord do
destination_attribute :resource_record_id
# where `Hr.People.Person` is determined automatically as the module running this extension
filter expr(resource_name == "Hr.People.Person")
end
Here is how far I could go. Still unable to apply filters in the transformers add_new_relationship function.
defmodule MyApp.Extensions.CustomerFields.Transformers.AddCustomerFields do
use Spark.Dsl.Transformer
def transform(dsl) do
Ash.Resource.Builder.add_new_relationship(
dsl,
:has_many,
:fields,
Hr.Fields.FieldResourceRecord,
destination_attribute: :resource_record_id
)
|> dbg()
end
end
Make sure to import Ash.Expr
import Ash.Expr
and then you can use it like so:
Ash.Resource.Builder.add_new_relationship(
dsl,
:has_many,
:fields,
Hr.Fields.FieldResourceRecord,
destination_attribute: :resource_record_id,
filter: expr(resource_name == "Hr.People.Person")
)
EDIT: I’m pretty sure that the filter option should “just work” like that, but let me know if not.
1 Like
It was not working. I fixed it like below:
defmodule Hr.Extensions.CustomFields.Transformers.AddCustomFields do
use Spark.Dsl.Transformer
import Ash.Expr
@doc """
Automatically add relationship to the FieldResourceRecord on any given resource
"""
def transform(dsl) do
Ash.Resource.Builder.add_new_relationship(
dsl,
:has_many,
:fields,
Hr.Fields.FieldResourceRecord,
destination_attribute: :resource_record_id,
filters: expr(resource_name == get_resource_name(dsl))
)
end
defp get_resource_name(dsl) do
dsl.persist.module
|> Atom.to_string()
|> String.replace("Elixir.", "")
end
end
Also the error message says it requires filter options even if it has been passed. So, I figured out that the issues is in the error messages.
The error message should report missing destination_attribute instead of destination_field, and it should report missing filters instead of filter.
Error showing missing filter instead of filters
Error showing destination field missing nstead of destination_attribute
1 Like