dmitriid
dmitriid
AEAsh Elixir
Created by dmitriid on 4/10/2023 in #support
protocol Enumerable not implemented for #Ash.Changeset
I apologize if I abuse the support channel, please tell me if I should take this to #general or #archive-debugging I've ran into this issue on two different resources, and I'll detail both below. The tl;dr is: I define a code_interface for a resource, but when I call the function it fails with protocol Enumerable not implemented for #Ash.Changeset<action_type: :create... at (ash 2.6.27) lib/ash/changeset/changeset.ex:998: Ash.Changeset.cast_params/3 Resource the first I want to generate an "external id" on creation, so I expose a new acton via code_interface.
defmodule Telepai.App.Session do
attributes do
integer_primary_key(:id)
attribute :external_id, :string
attribute :settings, :string
end

actions do
defaults([:create, :read, :update, :destroy])

create :new do
accept([:settings])

change(fn changeset, _ ->
now = NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
external_id = now |> HumanReadableIdentifierGenerator.fetch_human_readable_id!()

changeset |> Ash.Changeset.change_attribute(:external_id, external_id)
end)
end
end

code_interface do
define_for(__MODULE__)
define(:new)

# Note: if I don't define :create, compiler complains that
# `create/2` and `create!/2` are private or not defined
define(:create)
end
end
defmodule Telepai.App.Session do
attributes do
integer_primary_key(:id)
attribute :external_id, :string
attribute :settings, :string
end

actions do
defaults([:create, :read, :update, :destroy])

create :new do
accept([:settings])

change(fn changeset, _ ->
now = NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
external_id = now |> HumanReadableIdentifierGenerator.fetch_human_readable_id!()

changeset |> Ash.Changeset.change_attribute(:external_id, external_id)
end)
end
end

code_interface do
define_for(__MODULE__)
define(:new)

# Note: if I don't define :create, compiler complains that
# `create/2` and `create!/2` are private or not defined
define(:create)
end
end
Then calling it:
> Telepai.App.Session.new!(%{settings: "aaa"})
protocol Enumerable not implemented for #Ash.Changeset<action_type: :create, action: :new, attributes: %{external_id: "pingo-frangimini-51", settings: "aaa"}, relationships: %{}...
...
(ash 2.6.27) lib/ash/changeset/changeset.ex:998: Ash.Changeset.cast_params/3
(ash 2.6.27) lib/ash/changeset/changeset.ex:747: Ash.Changeset.do_for_action/4
(telepai 0.1.0) lib/ash/code_interface.ex:533: Telepai.App.Session.create!/2
> Telepai.App.Session.new!(%{settings: "aaa"})
protocol Enumerable not implemented for #Ash.Changeset<action_type: :create, action: :new, attributes: %{external_id: "pingo-frangimini-51", settings: "aaa"}, relationships: %{}...
...
(ash 2.6.27) lib/ash/changeset/changeset.ex:998: Ash.Changeset.cast_params/3
(ash 2.6.27) lib/ash/changeset/changeset.ex:747: Ash.Changeset.do_for_action/4
(telepai 0.1.0) lib/ash/code_interface.ex:533: Telepai.App.Session.create!/2
I'll continue with the second resource in the comments
11 replies
AEAsh Elixir
Created by dmitriid on 4/7/2023 in #support
Lookups/calcultions based on has_many relationships
I will try to be very precise and concise, so apologies if this comes off rude-ish. - The history table contains a long list of entires that a user enters the application. Imagine like a list of chat entries. - The users can bookmark any number of those history entries - when retrieving a list of entries (e.g., last 40 entries, or all entries in a paged manner) I would like to try and in one go retrieve both the entries and whether they were bookmarked by this particular user A corresponding SQL query would be something like
SELECT history.*,
CASE WHEN bookmarks.history_id IS NULL THEN 0 ELSE 1 END AS is_bookmarked
FROM history
LEFT JOIN bookmarks ON history.id = bookmarks.history_id;
SELECT history.*,
CASE WHEN bookmarks.history_id IS NULL THEN 0 ELSE 1 END AS is_bookmarked
FROM history
LEFT JOIN bookmarks ON history.id = bookmarks.history_id;
I have it sort of figured out with relationships and calculations, but this seems to load more data than I would actually like. Relevant parts only:
defmodule Telepai.App.History do
attributes do
integer_primary_key(:id)

attribute :data, :string do
allow_nil?(false)
constraints(trim?: true, max_length: 5000, allow_empty?: false)
end
end

relationships do
has_many :bookmarks, Telepai.Users.Bookmark do
api(Telepai.Users)

# I have played with hardcoded filters, too.
# But of course I don't want it hardcoded in the end :)
# filter(expr(user_id == 1))
end
end

calculations do
# hardcoded again
calculate(:is_bookmarked, :term, expr(bookmarks.user_id == 1))

# Or even this, but this needs the hardcoded filter in the relationship
# calculate(:is_bookmarked, :term, expr(not is_nil(bookmarks)))
end

end
defmodule Telepai.App.History do
attributes do
integer_primary_key(:id)

attribute :data, :string do
allow_nil?(false)
constraints(trim?: true, max_length: 5000, allow_empty?: false)
end
end

relationships do
has_many :bookmarks, Telepai.Users.Bookmark do
api(Telepai.Users)

# I have played with hardcoded filters, too.
# But of course I don't want it hardcoded in the end :)
# filter(expr(user_id == 1))
end
end

calculations do
# hardcoded again
calculate(:is_bookmarked, :term, expr(bookmarks.user_id == 1))

# Or even this, but this needs the hardcoded filter in the relationship
# calculate(:is_bookmarked, :term, expr(not is_nil(bookmarks)))
end

end
And the bookmarks:
defmodule Telepai.Users.Bookmark do
attributes do
integer_primary_key(:id)
end

relationships do
belongs_to :user, Telepai.Users.User do
allow_nil?(false)
end

belongs_to :history, Telepai.App.History do
api(Telepai.App.History)
allow_nil?(false)
end
end
end
defmodule Telepai.Users.Bookmark do
attributes do
integer_primary_key(:id)
end

relationships do
belongs_to :user, Telepai.Users.User do
allow_nil?(false)
end

belongs_to :history, Telepai.App.History do
api(Telepai.App.History)
allow_nil?(false)
end
end
end
Continued below
27 replies