lib/ash/query/calculation.ex

defmodule Ash.Query.Calculation do
  @moduledoc "Represents a calculated attribute requested on a query"

  defstruct [
    :name,
    :module,
    :opts,
    :load,
    :type,
    :constraints,
    :calc_name,
    context: %{},
    required_loads: [],
    select: [],
    filterable?: true,
    sortable?: true,
    sensitive?: false
  ]

  @type t :: %__MODULE__{}

  @opt_schema [
    arguments: [
      type: :map,
      doc: "Arguments to pass to the calculation",
      default: %{}
    ],
    filterable?: [
      type: :boolean,
      doc: "Whether or not this calculation can be filtered on",
      default: true
    ],
    sortable?: [
      type: :boolean,
      doc: "Whether or not this calculation can be sorted on",
      default: true
    ],
    sensitive?: [
      type: :boolean,
      doc: "Whether or not references to this calculation will be considered sensitive",
      default: false
    ],
    load: [
      type: :any,
      doc: "Loads that are required for the calculation."
    ],
    source_context: [
      type: :map,
      doc: "Context from the source query or changeset.",
      default: %{}
    ]
  ]

  @doc """
  Creates a new query calculation.

  ## Options

  #{Spark.Options.docs(@opt_schema)}
  """
  def new(
        name,
        module,
        calc_opts,
        type,
        constraints,
        opts \\ []
      ) do
    with {:ok, opts} <- Spark.Options.validate(opts, @opt_schema),
         {:ok, calc_opts} <- module.init(calc_opts) do
      context = %Ash.Resource.Calculation.Context{
        arguments: opts[:arguments],
        type: type,
        constraints: constraints,
        source_context: opts[:source_context] || %{}
      }

      {:ok,
       %__MODULE__{
         name: name,
         module: module,
         type: type,
         opts: calc_opts,
         calc_name: name,
         constraints: constraints,
         context: context,
         required_loads: opts[:load],
         filterable?: opts[:filterable?],
         sortable?: opts[:sortable?],
         sensitive?: opts[:sensitive?]
       }}
    end
  end

  defimpl Inspect do
    import Inspect.Algebra

    def inspect(%{module: module, opts: calculation_opts, context: context}, _opts) do
      if context.arguments == %{} do
        module.describe(calculation_opts)
      else
        concat([module.describe(calculation_opts), " - ", inspect(context.arguments)])
      end
    end
  end
end