Hello,
I am having an error hard to debug, I don’t understand what is going on.
I have create an Ash.Type
for Postgrex.Interval
:
defmodule CELP.Types.Interval do
use Ash.Type
alias Postgrex.Interval
# Returns the underlying storage type (the underlying type of the ecto type of the ash type)
@impl Ash.Type
def storage_type(_), do: :interval
# Casts input (e.g. unknown) data to an instance of the type, or errors
# Cast the data into something used at runtime. It's like `cast` in Ecto Types.
@impl Ash.Type
@spec cast_input(nil | integer() | String.t(), term()) ::
{:ok, nil | Postgrex.Interval.t()} | {:error, String.t()}
def cast_input(nil, _), do: {:ok, nil}
def cast_input(value, _) when is_integer(value) do
{:ok, minutes_to_interval(value)}
end
def cast_input(value, _) when is_binary(value) do
case Integer.parse(value) do
{minutes, _} -> {:ok, minutes_to_interval(minutes)}
:error -> {:error, "invalid_interval. Input must be a valid integer in string format."}
end
end
def cast_input(_, _) do
{:error, "invalid_interval. Input must be an integer or a string representing an integer."}
end
# Casts a value from the data store to an instance of the type, or errors
# This is like the load in Ecto types.
@impl Ash.Type
@spec cast_stored(nil | Postgrex.Interval.t(), term()) ::
{:ok, nil | integer()} | {:error, String.t()}
def cast_stored(nil, _), do: {:ok, nil}
def cast_stored(%Interval{} = interval, _) do
{:ok, interval_to_minutes(interval)}
end
def cast_stored(_, _),
do: {:error, "invalid_stored_value. Expected a Postgrex.Interval struct."}
# Casts a value from the Elixir type to a value that the data store can persist
# This is like the dump in Ecto types. We have the value casted used in `cast_input`.
@impl Ash.Type
@spec dump_to_native(nil | Postgrex.Interval.t(), term()) ::
{:ok, nil | Postgrex.Interval.t()} | {:error, String.t()}
def dump_to_native(nil, _), do: {:ok, nil}
def dump_to_native(%Interval{} = interval, _), do: {:ok, interval_to_minutes(interval)}
def dump_to_native(_, _),
do: {:error, "invalid_native_value. Expected a Postgrex.Interval struct."}
# Helper function to convert minutes to Postgrex.Interval
@spec minutes_to_interval(integer()) :: Postgrex.Interval.t()
defp minutes_to_interval(minutes) do
total_seconds = minutes * 60
days = div(total_seconds, 86_400)
remaining_seconds = rem(total_seconds, 86_400)
%Interval{
# Approximate months as 30-day units
months: div(days, 30),
# Remaining days after extracting months
days: rem(days, 30),
# Remaining seconds within the day
secs: remaining_seconds
}
end
# Converts a Postgrex.Interval to total minutes
@spec interval_to_minutes(Postgrex.Interval.t()) :: integer() | {:error, String.t()}
def interval_to_minutes(%Postgrex.Interval{months: months, days: days, secs: secs}) do
# Assuming each month has 30 days
total_days = months * 30 + days
total_seconds = total_days * 86_400 + secs
# Convert seconds to minutes
div(total_seconds, 60)
end
def interval_to_minutes(_), do: {:error, "Invalid input. Expected a Postgrex.Interval struct."}
defimpl String.Chars, for: [Postgrex.Interval] do
import Kernel, except: [to_string: 1]
def to_string(%{:months => months, :days => days, :secs => secs}) do
m =
if months === 0 do
""
else
" #{months} months"
end
d =
if days === 0 do
""
else
" #{days} days"
end
s =
if secs === 0 do
""
else
" #{secs} seconds"
end
if months === 0 and days === 0 and secs === 0 do
"<None>"
else
"Every#{m}#{d}#{s}"
end
end
end
end
I have added the attribute duration using this new Type:
attributes do
uuid_primary_key :id
attribute :day_of_week, :integer do
description "The day of the week. 1 is Monday, 7 is Sunday"
allow_nil? false
public? true
end
attribute :time, :time do
allow_nil? false
public? true
end
attribute :duration, CELP.Types.Interval do
allow_nil? false
public? true
end
timestamps()
end
The update
action is very straightforward:
update :update do
primary? true
accept [
:day_of_week,
:time,
:duration,
:worker_id
]
end
I am trying to update the value using AshPhoenix.Form.submit
and I get and error. Just in case I have tried to update the instance using this code:
Bookings.update_slot_template(
socket.assigns.slot_template,
slot_template_params["day_of_week"],
slot_template_params["time"],
slot_template_params["duration"],
slot_template_params["worker_id"]
)
But I always get this error:
Slot template params: %{
"day_of_week" => "1",
"duration" => "1",
"time" => "07:00:00",
"worker_id" => "ac908816-2a00-4e37-a0cc-6610dc3b9da5"
}
Updated slot template: {:error,
%Ash.Error.Invalid{
bread_crumbs: ["Returned from bulk query update: CELP.Bookings.SlotTemplate.update"],
changeset: "#Changeset<>",
errors: [
%Ash.Error.Query.InvalidFilterValue{
message: nil,
value: %Postgrex.Interval{months: 0, days: 0, secs: 60, microsecs: 0},
context: #Ecto.Changeset<action: nil, changes: %{}, errors: [],
data: #CELP.Bookings.SlotTemplate<>, valid?: true, ...>,
splode: Ash.Error,
bread_crumbs: ["Returned from bulk query update: CELP.Bookings.SlotTemplate.update"],
vars: [],
path: [],
stacktrace: #Splode.Stacktrace<>,
class: :invalid
}
]
}}
I don’t get it. I have tried everything with the Type. But I don’t know why it’s filtering by the interval in this case.
What am I missing?