Can not use Ash Resource action with Ash Admin without Attributes

I have defined an ash resource that represents a remote api. I have an action that makes a specific query to the API and use typed structs for the result. I'd like to be able to interact with this through ash admin. But it is giving me an error that there's no struct. This appears to be because I have no attributes on this resource. Must I declare attributes to the resource? Is there a flaw in my approach? The moment I add the following to my resource, it works fine:
attributes do
attribute :id, :string, allow_nil?: true
end
attributes do
attribute :id, :string, allow_nil?: true
end
I may be going about this completely incorrectly, I would be interested in ANY feedback as this is exploratory work. Resource
defmodule DataHoard.Ingestion.Gmail do
use Ash.Resource,
otp_app: :data_hoard,
domain: DataHoard.Ingestion

alias DataHoard.Ingestion.Gmail.MessagesList.Message

resource do
require_primary_key? false
end

actions do
action :messages_list, {:array, DataHoard.Ingestion.Gmail.MessagesList.Message} do
argument :google_oauth_token, :string

run fn input, _context ->
with {:ok, messages_list} <-
DataHoard.Integrations.Google.Gmail.get_messages_list(
input.arguments.google_oauth_token
) do
messages = Enum.map(messages_list.messages, &Message.from_messages_list/1)
{:ok, messages}
else
{:error, error} -> {:error, error.message}
end
end
end
end
end
defmodule DataHoard.Ingestion.Gmail do
use Ash.Resource,
otp_app: :data_hoard,
domain: DataHoard.Ingestion

alias DataHoard.Ingestion.Gmail.MessagesList.Message

resource do
require_primary_key? false
end

actions do
action :messages_list, {:array, DataHoard.Ingestion.Gmail.MessagesList.Message} do
argument :google_oauth_token, :string

run fn input, _context ->
with {:ok, messages_list} <-
DataHoard.Integrations.Google.Gmail.get_messages_list(
input.arguments.google_oauth_token
) do
messages = Enum.map(messages_list.messages, &Message.from_messages_list/1)
{:ok, messages}
else
{:error, error} -> {:error, error.message}
end
end
end
end
end
Domain
defmodule DataHoard.Ingestion do
use Ash.Domain,
otp_app: :data_hoard,
extensions: [AshAdmin.Domain]

admin do
show? true
end

resources do
resource DataHoard.Ingestion.Gmail
end
end
defmodule DataHoard.Ingestion do
use Ash.Domain,
otp_app: :data_hoard,
extensions: [AshAdmin.Domain]

admin do
show? true
end

resources do
resource DataHoard.Ingestion.Gmail
end
end
Error
[error] ** (UndefinedFunctionError) function DataHoard.Ingestion.Gmail.__struct__/0 is undefined or private
(data_hoard 0.1.0) DataHoard.Ingestion.Gmail.__struct__()
(ash_phoenix 2.3.17) lib/ash_phoenix/form/form.ex:515: AshPhoenix.Form.for_action/3
(ash_admin 0.13.23) lib/ash_admin/components/resource/generic_action.ex:248: AshAdmin.Components.Resource.GenericAction.update/2
(phoenix_live_view 1.1.16) lib/phoenix_live_view/utils.ex:492: Phoenix.LiveView.Utils.maybe_call_update!/3
(elixir 1.19.1) lib/enum.ex:1688: Enum."-map/2-lists^map/1-1-"/2
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:859: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
(telemetry 1.3.0) /home/shawn/src/data-hoard/deps/telemetry/src/telemetry.erl:324: :telemetry.span/3
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:854: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
(stdlib 7.1) maps.erl:894: :maps.fold_1/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:813: Phoenix.LiveView.Diff.render_pending_components/6
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:153: Phoenix.LiveView.Diff.render/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/static.ex:291: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/static.ex:171: Phoenix.LiveView.Static.do_render/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.8.1) lib/phoenix/router.ex:416: Phoenix.Router.__call__/5
(data_hoard 0.1.0) lib/data_hoard_web/endpoint.ex:1: DataHoardWeb.Endpoint.plug_builder_call/2
(data_hoard 0.1.0) deps/plug/lib/plug/debugger.ex:155: DataHoardWeb.Endpoint."call (overridable 3)"/2
(data_hoard 0.1.0) lib/data_hoard_web/endpoint.ex:1: DataHoardWeb.Endpoint.call/2
(phoenix 1.8.1) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(bandit 1.8.0) lib/bandit/pipeline.ex:131: Bandit.Pipeline.call_plug!/2
request_id=GHfbY0jAql9wLeEAAB9B mfa=Bandit.Pipeline.handle_error/7
[error] ** (UndefinedFunctionError) function DataHoard.Ingestion.Gmail.__struct__/0 is undefined or private
(data_hoard 0.1.0) DataHoard.Ingestion.Gmail.__struct__()
(ash_phoenix 2.3.17) lib/ash_phoenix/form/form.ex:515: AshPhoenix.Form.for_action/3
(ash_admin 0.13.23) lib/ash_admin/components/resource/generic_action.ex:248: AshAdmin.Components.Resource.GenericAction.update/2
(phoenix_live_view 1.1.16) lib/phoenix_live_view/utils.ex:492: Phoenix.LiveView.Utils.maybe_call_update!/3
(elixir 1.19.1) lib/enum.ex:1688: Enum."-map/2-lists^map/1-1-"/2
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:859: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
(telemetry 1.3.0) /home/shawn/src/data-hoard/deps/telemetry/src/telemetry.erl:324: :telemetry.span/3
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:854: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
(stdlib 7.1) maps.erl:894: :maps.fold_1/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:813: Phoenix.LiveView.Diff.render_pending_components/6
(phoenix_live_view 1.1.16) lib/phoenix_live_view/diff.ex:153: Phoenix.LiveView.Diff.render/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/static.ex:291: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/static.ex:171: Phoenix.LiveView.Static.do_render/4
(phoenix_live_view 1.1.16) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.8.1) lib/phoenix/router.ex:416: Phoenix.Router.__call__/5
(data_hoard 0.1.0) lib/data_hoard_web/endpoint.ex:1: DataHoardWeb.Endpoint.plug_builder_call/2
(data_hoard 0.1.0) deps/plug/lib/plug/debugger.ex:155: DataHoardWeb.Endpoint."call (overridable 3)"/2
(data_hoard 0.1.0) lib/data_hoard_web/endpoint.ex:1: DataHoardWeb.Endpoint.call/2
(phoenix 1.8.1) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(bandit 1.8.0) lib/bandit/pipeline.ex:131: Bandit.Pipeline.call_plug!/2
request_id=GHfbY0jAql9wLeEAAB9B mfa=Bandit.Pipeline.handle_error/7
Solution:
Short version: yes, you need to declare at least one attribute so Ash can generate a struct for your resource
Jump to solution
5 Replies
Solution
Gonzalo Muñoz
Short version: yes, you need to declare at least one attribute so Ash can generate a struct for your resource
Gonzalo Muñoz
DeepWiki
Search | DeepWiki
DeepWiki provides up-to-date documentation you can talk to, for every repo in the world. Think Deep Research for GitHub - powered by Devin.
Shawn McCool
Shawn McCoolOP2w ago
May I ask, would you consider this approach? Would you prefer a single ash resource for a single api endpoint, or would you pefer to not have ash resources manage these kind of api calls? As I mentioned, this is fully exploratory work at this moment. I'm just experimenting with Ash.
Gonzalo Muñoz
Yeah, the thing with Ash is, even if what you're doing doesn't map 100% to the concept of a resource, you get the whole ecosystem for free. For example, that action you wrote can become an AI tool with like one extra line. Also, a TS interface or GraphQL operation, or whatever really. With a couple of extra config lines. So yes, it does make sense doing it this way, for me at least
Shawn McCool
Shawn McCoolOP2w ago
Thanks for the feedback. I'm currently trying to just.. jam everything into Ash and see where I can find cracks tbh.

Did you find this page helpful?