Hey. I have an update and some custom changes. As updates try to run atomic i tried to add the atomic function to the change. For some reason if i update a record the change is not being applied/updated. When i save the update a second time without changing anything then the change is being applied/updated.
actions do
update :update do
accept [:subject]
end
end
changes do
change {Elephouse.TodoList.Changes.Title, attribute: :title}
change {Elephouse.TodoList.Changes.Hashtags, attribute: :hashes}
change {Elephouse.TodoList.Changes.DueDate, attribute: :due_at}
end
One of the Changes:
defmodule Elephouse.TodoList.Changes.Title do
use Ash.Resource.Change
@impl true
def init(opts) do
if is_atom(opts[:attribute]) do
{:ok, opts}
else
{:error, "attribute must be an atom!"}
end
end
@impl true
def change(changeset, opts, _context) do
subject = Ash.Changeset.get_argument_or_attribute(changeset, :subject)
case Elephouse.TodoList.Helpers.TitleParser.parse(subject) do
{:ok, title} ->
Ash.Changeset.force_change_attribute(changeset, opts[:attribute], title)
_ ->
changeset
end
end
@impl true
def atomic(changeset, opts, _context) do
subject = Ash.Changeset.get_argument_or_attribute(changeset, :subject)
case Elephouse.TodoList.Helpers.TitleParser.parse(subject) do
{:ok, title} -> {:atomic, %{opts[:attribute] => title}}
_ -> changeset
end
end
end
And the TitleParser:
defmodule Elephouse.TodoList.Helpers.TitleParser do
def parse(nil), do: {:ok, ""}
def parse(subject) do
title = subject
|> String.split()
|> Enum.reject(&String.starts_with?(&1, "due:"))
|> Enum.reject(&String.starts_with?(&1, "#"))
|> Enum.join(" ")
{:ok, title}
end
end
In the atomic/3 callback, what does subject equal after you assign it? What is the result of parsing the title? atomic/3 changes work differently from regular changes, and so in your atomic update scenario, depending on how you are doing it, you may not hav access to the :subject attribute, as it may be in the atomics change list.
Your Elephouse.TodoList.Changes.Title as it stands now is not really atomic compatible because its using get_argument_or_attribute which falls back to the old value of an attribute.
The ergonomics here are not ideal, because there is a way for you to do what you want, but it depends on the new title being an argument, not an attribute. Then its up to your action to handle it and it isn’t written into the atomics change list. Then you can do something like:
case Ash.Changeset.fetch_argument(changeset, :subject) do
{:ok, new_subject} ->
case Elephouse.TodoList.Helpers.TitleParser.parse(subject) do
{:ok, title} -> {:atomic, %{opts[:attribute] => title}}
_ -> {:ok, changeset}
end
_ ->
{:ok, changeset}
end