Atomic update with custom change is not updating on first run

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

I appreciate any help. Thanks

When you say its not being run, do you mean that its not having the effect you want it to have or that function itself is actually not being called?

Yeah. I mean that its not having the effect i want it to have.

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

subject equals to something around “Item title hash” and the result of parsing should be title equals only “Item title

:thinking: interesting. I might need to see some kind of reproduction of the issue that you’re facing, it’s hard to diagnose otherwise.

Ok. I tested it again and it is still not working on my side.

I changed the custom change to:

def atomic(changeset, opts, _context) do
    subject = Ash.Changeset.atomic_ref(changeset, :subject)

    IO.inspect(Ash.Expr.expr(^subject), label: "BEFORE PARSING")

    case Elephouse.TodoList.Helpers.HashtagParser.parse(subject) do
      {:ok, hashes} -> {:atomic, %{opts[:attribute] => hashes}}
      _ -> {:ok, changeset}
    end
  end

How do i get the change from the atomic_ref and not the whole reference?
Or how do i get the input change when an atomic change is running?

I need to do some other things with the inputed data before saving.