Read action by related struct

Your original example actually has an interesting issue that comes down to how Elixir as a language works

filter expr(definition_id == ^arg(:definition)[:id])

maps to this

filter expr(definition_id == ^(arg(:definition)[:id]))

But that isn’t what you want. thing[:id] is a valid expression in Ash’s expression syntax. But to use it on something pinned, you need to add parenthesis

filter expr(definition_id == (^arg(:definition))[:id])

As for accepting multiple types, Ash has a built in union type for that. I wouldn’t suggest using :term in general, but it is there to be an escape hatch for cases where it matters.

    read :by_definition do
      argument :definition, :union do
        allow_nil? false
        constraints [
         types: [
           definition: [type: :struct, constraints: [instance_of: Definition]],
           definition_id: [type: :uuid]
         ]
       ]
      end

      prepare fn query, _ ->
        definition_id = 
          case query.arguments.definition do
            %Ash.Union{type: :definition_id, value: definition_id} -> definition_id
            %Ash.Union{type: :definition, value: %{id: definition_id}} -> definition_id
          end

        Ash.Query.filter(query, definition_id == ^definition_id
     end
  end

It is a bit verbose, but there are ways to shrink it down and/or extract it into something reusable.