Better Way to `set_attribute` that the modify function takes more than one arguments?

Hello,

I have a create action that takes 2 arguments, set_attribute. Here is the action:

create :create_company do
      primary? true

      accept [:name, :registration_number]

      description "Automatically create a unique company registration number if generate..."

      argument :generate_registration_number, :boolean, default: false

      argument :country, :string, allow_nil?: false

      change set_attribute(:registration_number, quote do
        generate_short_hash(arg(:country), arg(:name))
      end) do
        where [argument_equals(:generate_registration_number, true)]
      end
    end

Initially i tried with, but checking at the type @spec set_attribute( attribute :: atom(), (-> term()) | {:_arg, :status} | term(), opts :: Keyword.t() ) :: Ash.Resource.Change.ref(), thought that it takes zero argument function

change set_attribute(:registration_number, &generate_short_hash(arg(:country), arg(:name)   do 
where [argument_equals(:generate_registration_number, true)]
      end

The compilation error i get:

[{
	"resource": "/home/zer3f/MGX/lib/mgx/companies/companies_information.ex",
	"owner": "_generated_diagnostic_collection_name_#0",
	"severity": 8,
	"message": "** (MismatchedDelimiterError) mismatched delimiter found on lib/mgx/companies/companies_information.ex:37:5:\n    error: unexpected reserved word: end\n    β”‚\n 34 β”‚       change set_attribute(:registration_number, &Mgx.Utils.Generator.generate_short_hash(arg(:country), arg(:name)) do\n    β”‚                           β”” unclosed delimiter\n 35 β”‚         where [argument_equals(:generate_registration_number, true)]\n 36 β”‚       end\n 37 β”‚     end\n    β”‚     β”” mismatched closing delimiter (expected \")\")\n    β”‚\n    └─ lib/mgx/companies/companies_information.ex:37:5",
	"source": "Elixir",
	"startLineNumber": 34,
	"startColumn": 27,
	"endLineNumber": 37,
	"endColumn": 5,
	"relatedInformation": [
		{
			"startLineNumber": 34,
			"startColumn": 27,
			"endLineNumber": 34,
			"endColumn": 28,
			"message": "opening delimiter: (",
			"resource": "/home/zer3f/MGX/lib/mgx/companies/companies_information.ex"
		},
		{
			"startLineNumber": 37,
			"startColumn": 5,
			"endLineNumber": 37,
			"endColumn": 8,
			"message": "closing delimiter: end",
			"resource": "/home/zer3f/MGX/lib/mgx/companies/companies_information.ex"
		}
	]
}]

But i wonder if there is other way might be better way to do this.

You’re likely getting the error because you are passing in a quote do ... block as the value to set_attribute in the first example and you can only pass zero arity functions as the value. Otherwise you have to use the anonymous function change or a change module.

try this

change fn changeset, _context ->
  country = Ash.Changeset.get_argument(changeset, :country)
  name = Ash.Changeset.get_attribute(changeset, :name)

  hash = generate_short_hash(country, name)
  Ash.Changeset.change_attribute(changeset, :registration_number, hash)
end,
  where: argument_equals(:generate_registration_number, true)

If this anon function works, I suggest creating a change module to make things a bit cleaner.

defmodule MyApp.Domain.Company.Changes.GenerateRegistrationNumber do
  use Ash.Resource.Change

  @impl true
  def change(changeset, _opts, _context) do
    country = Ash.Changeset.get_argument(changeset, :country)
    name = Ash.Changeset.get_attribute(changeset, :name)

    hash = generate_short_hash(country, name)
    Ash.Changeset.change_attribute(changeset, :registration_number, hash)
  end
end

Then you can add the change in the resource like this

change MyApp.Domain.Company.Changes.GenerateRegistrationNumber,
  where: argument_equals(:generate_registration_number, true)
2 Likes

I see. Indeed quote gave an error. Thank you.