lib/ash/data_layer/simple/simple.ex

defmodule Ash.DataLayer.Simple do
  @moduledoc """
  A data layer that simply returns structs

  This is the data layer that is used under the hood
  by embedded resources, and resources without data layers.
  """

  use Ash.Dsl.Extension, transformers: [], sections: []

  def show_in_docs?, do: false

  def can?(_, :create), do: true
  def can?(_, :update), do: true
  def can?(_, :destroy), do: true
  def can?(_, :sort), do: true
  def can?(_, :limit), do: true
  def can?(_, {:sort, _}), do: true
  def can?(_, :filter), do: true
  def can?(_, :boolean_filter), do: true
  def can?(_, :nested_expressions), do: true
  def can?(_, {:filter_expr, _}), do: true
  def can?(_, :multitenancy), do: true
  def can?(_, _), do: false

  defmodule Query do
    @moduledoc false
    defstruct [:data, :resource, :filter, :api, :limit, sort: [], data_set?: false]
  end

  @doc """
  Sets the data for a query against a data-layer-less resource
  """
  def set_data(query, data) do
    Ash.Query.put_context(query, :data, data)
  end

  def resource_to_query(resource, api) do
    %Query{data: [], resource: resource, api: api}
  end

  def run_query(%{data_set?: false}, resource) do
    {:error,
     Ash.Error.SimpleDataLayer.NoDataProvided.exception(
       message:
         "No data provided to resource #{resource}\nA common cause of this is not having a data layer for the resource.\n\nYou can create a data layer by including the following in your resource:\n`use Ash.Resource, data_layer: Ash.DataLayer.Ets`"
     )}
  end

  def run_query(%{data: data, sort: sort, api: api, filter: filter, limit: limit}, _resource) do
    {:ok,
     data
     |> Enum.filter(&Ash.Filter.Runtime.matches?(api, &1, filter))
     |> Ash.Actions.Sort.runtime_sort(sort)
     |> then(fn data ->
       if limit do
         Enum.take(data, limit)
       else
         data
       end
     end)}
  end

  def limit(query, limit, _) do
    {:ok, %{query | limit: limit}}
  end

  def set_tenant(_, query, _), do: {:ok, query}

  def filter(query, filter, _resource) do
    {:ok, %{query | filter: filter}}
  end

  def sort(query, sort, _resource) do
    {:ok, %{query | sort: sort}}
  end

  def set_context(_resource, query, context) do
    case Map.fetch(context, :data) do
      {:ok, value} ->
        {:ok, %{query | data_set?: true, data: value || []}}

      :error ->
        {:ok, query}
    end
  end

  def create(_resource, changeset) do
    Ash.Changeset.apply_attributes(changeset)
  end

  def update(_resource, changeset) do
    Ash.Changeset.apply_attributes(changeset)
  end

  def destroy(_resource, _changeset) do
    :ok
  end
end