docs: rework resources w/o data layers how to guide

This commit is contained in:
Zach Daniel 2024-04-08 17:42:27 -04:00
parent a8f41393c9
commit 096535a436
4 changed files with 104 additions and 64 deletions

View file

@ -87,6 +87,8 @@ Welcome to the Ash Framework documentation! Here you will find everything you ne
## How To
- [Use Resources without Data Layers](documentation/how_to/use-resources-without-data-layers.md)
---
## Reference

View file

@ -0,0 +1,96 @@
# 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
```elixir
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.
```elixir
# 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.
```elixir
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!

View file

@ -1,58 +0,0 @@
# Use Without Data Layers
If a resource is configured without a data layer, then it will always be working off of a temporary data set that lives only for the life of that query. This can be a powerful way to model input validations and/or custom/complex reads. Technically, resources without a data layer use `Ash.DataLayer.Simple`, which does no persistence, and expects to find any data it should use for read actions in a context on the query
## Example
```elixir
defmodule MyApp.MyComplexResource do
use Ash.Resource
# notice no data layer is configured
attributes do
#A primary key is always necessary on a resource, but this will generate one for you automatically
uuid_primary_key :id
attribute :some_complex_derived_number, :integer
end
actions do
read :read do
prepare MyApp.FetchComplexResources
end
create :validate_input do
...
# will validate required inputs, and you can add
# validations like you would for any normal resource
end
end
end
defmodule MyApp.FetchComplexResources do
use Ash.Resource.Preparation
def prepare(query, _, _) do
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
```
## Usage
They are used in exactly the same way as regular resources
```elixir
# You can construct changeset over them
changeset =
Ash.Changeset.for_create(MyApp.FetchComplexResource, :validate_input, %{})
# This will return the structs by default
# Although you are free to do custom persistence in your resource changes
Ash.create!(changeset)
# %MyApp.FetchComplexResource{...}
```

12
mix.exs
View file

@ -79,6 +79,7 @@ defmodule Ash.MixProject do
"documentation/topics/security/policies.md",
"documentation/topics/reference/glossary.md",
"documentation/topics/reference/expressions.md",
"documentation/how_to/use-resources-without-data-layers.md",
"documentation/dsls/DSL:-Ash.Resource.md",
"documentation/dsls/DSL:-Ash.Domain.md",
"documentation/dsls/DSL:-Ash.Notifier.PubSub.md",
@ -87,9 +88,7 @@ defmodule Ash.MixProject do
"documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md",
"documentation/dsls/DSL:-Ash.Reactor.md",
"documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md",
"CHANGELOG.md",
# below this line under review
"documentation/how_to/use-without-data-layers.md",
"CHANGELOG.md"
],
groups_for_extras: [
"Start Here": [
@ -144,7 +143,9 @@ defmodule Ash.MixProject do
"documentation/topics/advanced/multitenancy.md",
"documentation/topics/advanced/writing-extensions.md"
],
"How To": [],
"How To": [
"documentation/how-to/use-resources-without-data-layers.md"
],
Reference: [
"documentation/topics/reference/expressions.md",
"documentation/topics/reference/glossary.md",
@ -157,8 +158,7 @@ defmodule Ash.MixProject do
"documentation/dsls/DSL:-Ash.DataLayer.Mnesia.md"
],
"Under Review": [
# Documentation below this line is pending review
"documentation/how_to/use-without-data-layers.md"
"documentation/how-to/use-without-data-layers.md"
]
],
skip_undefined_reference_warnings_on: [