Ash Resource not working on reads after migrating to 3.0

I was working on migrating over my app to use 3.0 and I’m running into a weird error. I have a few resource and it seems like most work as expected but one. The resource todo is able to properly create things but not load them. The only difference that might be relevant is that todo uses AshArchival.

todo = Todo.create!(...)
# returns todo
Ash.get(Todo, todo.id)
# returns %Ash.Error.Query.NotFound
Repo.get!(Todo, todo.id)
# returns entity properly with Ecto

The DB queries it forms

# from Ash
SELECT * FROM "todos" AS t0 WHERE (t0."id"::uuid = $1::uuid) LIMIT $2 ["e8fe6f7f-4349-471b-b94a-e36c23cc3f7d", 2]

# from Ecto
SELECT * FROM "todos" AS t0 WHERE (t0."id" = $1) ["e8fe6f7f-4349-471b-b94a-e36c23cc3f7d"]


# working project query from Ash
SELECT * FROM "projects" AS p0 WHERE (p0."id"::uuid = $1::uuid) LIMIT $2 ["ebcb9206-85a8-4686-aa0f
-2eb4e34e5806", 2]

Hmm…those queries don’t really make much sense as they should be completely equivalent.

My suspicion is that you are seeing here is the application of policies to your resource actions.

In Ash 3.0, the default value for authorize? was changed to true. In Ash 2.0, authorization would occur when you either provided actor: <something> or authorize?: true.

Do you have any policies on the resource in question?

I don’t have any policies on the resource. I could be wrong but after some trial and error it looks like it was linked to preparation/calculations but I would need to take a look at it.

  calculations do
    calculate(:days_until, :integer, TimeUntil)
  end

  preparations do
    # commenting this line seems to fix it
    # prepare(build(load: :days_until))
  end

I believe the issue was tied to the calculation that I had. I wanted to conditionally calculate something so I would filter out all the records that did not apply.

  @impl true
  def calculate(records, _opts, _params) do
    records
    |> Enum.filter(& &1.due_date_at)
    |> Enum.map(fn todo_record ->
      DateTime.diff(todo_record.due_date_at, DateTime.utc_now(), :day)
    end)
  end
end

I reimplemented to set an integer or nil and it seems to work

  @impl true
  def calculate(records, _opts, _params) do
    records
    |> Enum.map(fn todo_record ->
      calculate_diff(todo_record)
    end)
  end

  defp calculate_diff(%{due_date_at: due_date}) when is_nil(due_date) do
    nil
  end

  defp calculate_diff(%{due_date_at: due_date}) do
      DateTime.diff(due_date, DateTime.utc_now(), :day)
  end

Ah, interesting…yeah that would have done it. We zip the records up and that could result in missing records. We could add some checks in here that you returned the right number of records, but that would add expensive checks to a hot path, so probably not a good idea.