mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
docs: rework resources w/o data layers how to guide
This commit is contained in:
parent
a8f41393c9
commit
096535a436
4 changed files with 104 additions and 64 deletions
|
@ -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
|
||||
|
|
96
documentation/how-to/use-resources-without-data-layers.md
Normal file
96
documentation/how-to/use-resources-without-data-layers.md
Normal 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!
|
|
@ -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
12
mix.exs
|
@ -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: [
|
||||
|
|
Loading…
Reference in a new issue