AE
Ash Elixir•2y ago
miguels

Persist embedded resources as :text instead of :map

In my app I'm using Cocktail (https://hexdocs.pm/cocktail) to manage scheduled events which I'd like to save to a text field using Cocktail.Schedule.to_i_calendar/1. What I have so far: I use an embedded schema to generate the API used in LV forms and GraphQL
defmodule Schedule do
use Ash.Resource,
data_layer: :embedded,
extensions: [AshGraphql.Resource]

graphql do
type :schedule
end

attributes do
attribute :count, :integer
// ...
end

validations do
validate numericality(:count, greater_than: 0)
end
end
defmodule Schedule do
use Ash.Resource,
data_layer: :embedded,
extensions: [AshGraphql.Resource]

graphql do
type :schedule
end

attributes do
attribute :count, :integer
// ...
end

validations do
validate numericality(:count, greater_than: 0)
end
end
Which I use in another resource
attributes do
integer_primary_key :id

// ...

attribute :schedule, Schedule

// ...
end
attributes do
integer_primary_key :id

// ...

attribute :schedule, Schedule

// ...
end
And then realized that embedded schemas are persisted as maps and I haven't found a way to change this behaviour
def up do
alter table(:table) do
add :schedule, :map
end
end
def up do
alter table(:table) do
add :schedule, :map
end
end
I've also briefly tried setting up a custom type instead without much success.
8 Replies
barnabasj
barnabasj•2y ago
Why do you need it to be a string in the data_layer? Or rather, what are you trying to achieve? There are probably ways to make this work, but I think I'm not understanding the whole problem here.
miguels
miguelsOP•2y ago
I was looking to store the schedule as standard iCalendar RRULE for example "DTSTART:20230622T000000\nRRULE:FREQ=WEEKLY;INTERVAL=2"
barnabasj
barnabasj•2y ago
What you could do is use just string as the type in your resource, and then take in the schedule datastructure as an argument and cast it into a string before writing it onto the attribute. Something like
actions do
create :create do
argument :schedule, :map

change fn changeset, _ ->
schedule = Ash.Changeset.get_argument(changeset, :schedule)
Ash.Changeset.change_attribute(changeset, :schedule, Cocktail.to_i_calendar(schedule))
end
end
actions do
create :create do
argument :schedule, :map

change fn changeset, _ ->
schedule = Ash.Changeset.get_argument(changeset, :schedule)
Ash.Changeset.change_attribute(changeset, :schedule, Cocktail.to_i_calendar(schedule))
end
end
or you could just save it as a map and add a calculation that turns it into a string during a read Or you could define your own custom type https://hexdocs.pm/ash/Ash.Type.html#module-defining-custom-types that knows how to take in the values and transforms them into a string
miguels
miguelsOP•2y ago
I tried defining a custom type but got very stuck when generating the APIs 😅 Ideally what I'd like to achieve is: 1. Have a nicely typed API in LV forms and GraphQL 2. Define an attribute on my resource that is a Cocktail.Schedule struct 3. Persist this Cocktail.Schedule struct as a string in the db (using the to_i_calendar function) So far I can get the nice API using an embedded resource and I can do the casting and parsing with a custom type but I haven't found the way to tie it all together
barnabasj
barnabasj•2y ago
Do you need it to be a string in the db for some kind of compatability reasons or what is your reasoning for not saving it as a :map (jsonb) column?
miguels
miguelsOP•2y ago
Don't have a strong reason. Thought the standard string representation would be easier to store than a complex struct.
barnabasj
barnabasj•2y ago
postgres is pretty good with json these days. and you get more functionality from a jsonb column than just text, you could potentially index the fields inside the struct if necesary and it's easier to filter If you don't have a strong reason I would just keep the current behaviour if everything else is working the way you want.
miguels
miguelsOP•2y ago
Ok thanks for the help

Did you find this page helpful?