Understanding :optionals on actions / code_interface

I think I must be misunderstanding what code_interface optionals are, and how I can use optional parameters in an action.

Based on the queries that are being generated, this just isn’t right:

  actions do
    read :for_sprint do
      argument :sprint_id, :uuid, allow_nil?: false
      argument :type, :atom, allow_nil?: true

      case arg(:type) do
        nil -> filter expr(sprint_id == ^arg(:sprint_id) and type in COE.Types.ActivityTypes.values)
        t -> filter expr(sprint_id == ^arg(:sprint_id) and type == ^t)
      end
    end

  code_interface do
    define_for COE.Walk

    define :for_sprint, args: [:sprint_id, {:optional, :type}]
  end

Which creates the following query – seems like the type parameter is not being evaluated in the case statement…?

   query: #Ash.Query<
     resource: COE.Walk.Activity,
     arguments: %{type: nil, sprint_id: "c103f804-043b-40e5-9368-55c3f9992280"},
     filter: #Ash.Filter<sprint_id == "c103f804-043b-40e5-9368-55c3f9992280" and type == nil>,

Any pointers on what I’m doing wrong appreciated.

You would need to do this as a preparation. The way you are doing it now is evaluating it at compile time.

    read :for_sprint do
       argument :sprint_id, :uuid, allow_nil?: false
       argument :type, :atom, allow_nil?: true

       prepare fn query, _ -> 
         sprint_id = Ash.Query.get_argument(query, :sprint_id)
         
         case Ash.Query.get_argument(query, :type) do
           nil -> Ash.Query.filter(sprint_id == ^sprint_id and type in COE.Types.ActivityTypes.values)
           t -> Ash.Query.filter(sprint_id == ^sprint_id and type == ^t)
          end
      end
    end

Aha… gotcha. I suspected it was something like that. Thanks!

I think @barnabasJ’s answer is definitely the clearer way to write this, but I do want to point out that expressions are “partially evaluated” before being executed in the data layer or in elixir, which allows for this to be equivalent to the functional preparation version. Use the function, as its easier to understand in general, but this property of Ash expressions comes in handy sometimes.

read :for_sprint do
  ...

  filter expr(
    if is_nil(^arg(:type)) do
      sprint_id == ^arg(:sprint_id) and type in ^COE.Types.ActivityTypes.values()
    else
      sprint_id == ^arg(:sprint_id) and type == ^arg(:type)
    end
  )
end
1 Like