Skip to content

Commit

Permalink
feat: full implementation of step by step tutorial wrt this proposed …
Browse files Browse the repository at this point in the history
…guide in ash-hq: ash-project/ash#504
  • Loading branch information
chrishop committed Feb 8, 2023
1 parent b6392d7 commit 50a195a
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[
import_deps: [:ecto, :phoenix],
import_deps: [:ecto, :phoenix, :ash, :ash_phoenix, :ash_postgres],
inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
subdirectories: ["priv/*/migrations"]
]
7 changes: 7 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
# General application configuration
import Config

# For backwards compatibility, the following configuration is required.
# see https://ash-hq.org/docs/guides/ash/latest/get-started#temporary-config for more details
config :ash, :use_all_identities_in_manage_relationship?, false

config :my_ash_phoenix_app,
ash_apis: [MyAshPhoenixApp.Blog]

config :my_ash_phoenix_app,
ecto_repos: [MyAshPhoenixApp.Repo]

Expand Down
7 changes: 7 additions & 0 deletions lib/my_ash_phoenix_app/blog.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule MyAshPhoenixApp.Blog do
use Ash.Api

resources do
registry MyAshPhoenixApp.Blog.Registry
end
end
11 changes: 11 additions & 0 deletions lib/my_ash_phoenix_app/blog/registry.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule MyAshPhoenixApp.Blog.Registry do
use Ash.Registry,
extensions: [
# This extension adds helpful compile time validations
Ash.Registry.ResourceValidations
]

entries do
entry MyAshPhoenixApp.Blog.Post
end
end
36 changes: 36 additions & 0 deletions lib/my_ash_phoenix_app/blog/resources/post.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule MyAshPhoenixApp.Blog.Post do
# Using Ash.Resource here turns this model into an Ash resource.
use Ash.Resource,
# Tells Ash you want this resource to store its data in postgres.
data_layer: AshPostgres.DataLayer

# The postgres keyword is specific to the AshPostgres module.
# It tells postgres what to call the table and to
# communicate with postgres through MyPhoenixApp.Repo
postgres do
# Tells postgres what to call the table
table "posts"
# Tells Ash how to interface with the postgres table
repo MyAshPhoenixApp.Repo
end

actions do
# Exposes default built in actions to modify the resource
defaults [:create, :read, :update, :destroy]
end

# Attributes are the simple pieces of data that exist on your resource
attributes do
# Add an autogenerated UUID primary key called `:id`.
uuid_primary_key :id
# Add a string type attribute called `:title`
attribute :title, :string do
# We don't want the title to ever be `nil`
allow_nil? false
end

# Add a string type attribute called `:content`
# If not specified content can be `nil`
attribute :content, :string
end
end
8 changes: 5 additions & 3 deletions lib/my_ash_phoenix_app/repo.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule MyAshPhoenixApp.Repo do
use Ecto.Repo,
otp_app: :my_ash_phoenix_app,
adapter: Ecto.Adapters.Postgres
use AshPostgres.Repo, otp_app: :my_ash_phoenix_app

def installed_extensions do
["uuid-ossp", "citext"]
end
end
9 changes: 7 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule MyAshPhoenixApp.MixProject do
version: "0.1.0",
elixir: "~> 1.12",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:gettext] ++ Mix.compilers(),
compilers: Mix.compilers(),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps()
Expand Down Expand Up @@ -48,7 +48,12 @@ defmodule MyAshPhoenixApp.MixProject do
{:telemetry_poller, "~> 1.0"},
{:gettext, "~> 0.18"},
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"}
{:plug_cowboy, "~> 2.5"},
{:ash, "~> 2.5.10"},
{:ash_postgres, "~> 1.3.6"},
{:ash_phoenix, "~> 1.1"},
# If using ElixirLS then including elixir_sense to enable Ash auto-complete
{:elixir_sense, github: "elixir-lsp/elixir_sense", only: [:dev, :test]}
]
end

Expand Down
21 changes: 21 additions & 0 deletions priv/repo/migrations/20230208045100_install_2_extensions.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule MyAshPhoenixApp.Repo.Migrations.Install2Extensions do
@moduledoc """
Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""

use Ecto.Migration

def up do
execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"")
execute("CREATE EXTENSION IF NOT EXISTS \"citext\"")
end

def down do
# Uncomment this if you actually want to uninstall the extensions
# when this migration is rolled back:
# execute("DROP EXTENSION IF EXISTS \"uuid-ossp\"")
# execute("DROP EXTENSION IF EXISTS \"citext\"")
end
end
21 changes: 21 additions & 0 deletions priv/repo/migrations/20230208045101_initial_migration.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule MyAshPhoenixApp.Repo.Migrations.InitialMigration do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""

use Ecto.Migration

def up do
create table(:posts, primary_key: false) do
add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true
add :title, :text, null: false
add :content, :text
end
end

def down do
drop table(:posts)
end
end
6 changes: 6 additions & 0 deletions priv/resource_snapshots/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"installed": [
"uuid-ossp",
"citext"
]
}
49 changes: 49 additions & 0 deletions priv/resource_snapshots/repo/posts/20230208045101.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v4()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "title",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "content",
"type": "text"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "87D935B8DCBAC75EC19644E18A0E59B47D5FF9AA5F89F0256DEAA793AB27578A",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.MyAshPhoenixApp.Repo",
"schema": null,
"table": "posts"
}
103 changes: 103 additions & 0 deletions test/my_ash_phoenix_app/blog/resources/post_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
defmodule MyAshPhoenixApp.Blog.PostTest do
use MyAshPhoenixApp.DataCase, async: true

test "testing blog post actions" do
### CREATE ACTION - create new blog post ###
# We create an Ash changeset we intent to use to create an Ash resource
create_post_changeset =
Ash.Changeset.for_create(
# We specify which resource we want to create a changeset for
MyAshPhoenixApp.Blog.Post,
# We name the specific create action
:create,
# We pass the attributes we want to initialise our resource with
%{title: "hello world"}
)

# will return:
#
# #Ash.Changeset<
# action_type: :create,
# action: :create,
# attributes: %{title: "hello world"},
# relationships: %{},
# errors: [],
# data: #MyAshPhoenixApp.Blog.Post<
# __meta__: #Ecto.Schema.Metadata<:built, "posts">,
# id: nil,
# title: nil,
# content: nil,
# aggregates: %{},
# calculations: %{},
# __order__: nil,
# ...
# >,
# valid?: true
# >

# This changeset is given to the Ash Api the resource belongs to.
# The Api then tries to create the resource specified in the changeset.
assert %{title: "hello world"} = MyAshPhoenixApp.Blog.create!(create_post_changeset)
# will return:
#
# #MyAshPhoenixApp.Blog.Post<
# __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
# id: "d70dd979-0b30-4a3f-beb2-2d5bb2e24af7",
# title: "hello world",
# content: nil,
# aggregates: %{},
# calculations: %{},
# __order__: nil,
# ...
# >

### READ ACTION - read blog post(s) ###
assert [first_post = %{title: "hello world"}] =
MyAshPhoenixApp.Blog.read!(MyAshPhoenixApp.Blog.Post)

# will return:
#
# [
# #MyAshPhoenixApp.Blog.Post<
# __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
# id: "d70dd979-0b30-4a3f-beb2-2d5bb2e24af7",
# title: "hello world",
# content: nil,
# aggregates: %{},
# calculations: %{},
# __order__: nil,
# ...
# >
# ]

### UPDATE ACTION - update existing blog post ###
# notice how you have to parse in an existing resource to the changeset
assert %{
title: "hello world",
content: "hello to you too!"
} =
Ash.Changeset.for_update(first_post, :update, %{content: "hello to you too!"})
|> MyAshPhoenixApp.Blog.update!()

# will return:
#
# #MyAshPhoenixApp.Blog.Post<
# __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
# id: "d70dd979-0b30-4a3f-beb2-2d5bb2e24af7",
# title: "hello world",
# content: "hello to you too!",
# aggregates: %{},
# calculations: %{},
# __order__: nil,
# ...
# >

### DELETE ACTION - delete existing blog post ###
assert :ok ==
Ash.Changeset.for_destroy(first_post, :destroy)
|> MyAshPhoenixApp.Blog.destroy!()

# verifying no rows in resource
assert [] == MyAshPhoenixApp.Blog.read!(MyAshPhoenixApp.Blog.Post)
end
end

0 comments on commit 50a195a

Please sign in to comment.