ash/documentation/how-to/use-resources-without-data-layers.md

2.8 KiB

Use Resources without Data Layers

If you don't explicitly set a data layer, then a resource will use Ash.DataLayer.Simple. In this way, technically, a resource always has a data layer. Ash.DataLayer.Simple is a data layer that does no persistence, but instead validates and returns structs for mutating actions, and must be manually provided with data to use.

Example

defmodule MyApp.Person do
  use Ash.Resource
  # notice no data layer is configured

  attributes do
    uuid_primary_key :id
    attribute :name, :string, allow_nil?: false, public?: true
    attribute :points, :integer, allow_nil?: false, public?: true
  end

  actions do
    read :read do
      prepare MyApp.FetchPeople
    end

    create :create do
      accept [:some]
    end
  end
end

defmodule MyApp.FetchPeople do
  use Ash.Resource.Preparation

  @fake_people [
    %MyApp.Person{
      id: Ash.UUID.generate(),
      name: "Joe Armstrong",
      points: 10000
    },
    %MyApp.Person{
      id: Ash.UUID.generate(),
      name: "José Valim",
      points: 10000
    }
  ]

  def prepare(query, _, _) do
    Ash.Query.before_action(query, fn query ->
      case fetch_data(query) do
        {:ok, data} ->
          Ash.DataLayer.Simple.set_data(query, data)
        {:error, error} ->
          Ash.Query.add_error(query, SomeBuiltinOrCustomAshError.exception(...))
      end
    end)
  end

  defp fetch_data(_query) do
    # you could fetch them from an external API here, but for this example
    # we will just return some static data.
    # Be sure to return instances of the resource!

    {:ok, @fake_people}
  end
end

Usage

They are used in exactly the same way as regular resources

Create/Update/Destroy

In the example below, we create one, although it is not persisted anywhere and will not be returned when reading. However, you could do custom persistence. If, for example, we were reading from an external API, you might post to an API in an after_action hook on the create.

# You can construct changeset over them
changeset =
Ash.Changeset.for_create(MyApp.Person, :create, %{name: "Dave Thomas", points: 10000})

# This will return the structs by default
# Although you are free to do custom persistence in your resource changes
Ash.create!(changeset)
# %MyApp.FetchComplexResource{...}

Reads

When reading, you can use the resource as you would any other resource.

Resource
|> Ash.Query.filter(contains(name, "José"))
|> Ash.read!()
#=> [%MyApp.Person{name: "José Valim", points: 10000}]

Notice how consumers of your resource (generally) don't need to care if the data is coming from a database, an external API, or static data somewhere. They get the same API, and you can do things like pagination, sorting, etc. in the same way. You can even add the AshGraphql or AshJsonApi extension to expose an external API in your own API!