I’ve been struggling to come up with an Ash aggregation or calculation that works on a Time attribute. The attribute in question:
attribute :duration, :time do
allow_nil? false
public? true
end
What I had thought would be a super-simple sum aggregation ended up… not working at all:
* ** (Protocol.UndefinedError) protocol Enumerable not implemented for type Integer. This protocol is implemented for the following type(s): DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Iter, Jason.OrderedObject, LazyHTML, List, Map, MapSet, Phoenix.LiveView.LiveStream, Postgrex.Stream, Range, Req.Response.Async, Rewrite, Stream, StreamData
Which led me down a path of a custom implementation, which became messy. Then I switched to a calculation. My calculation isn’t working yet, but it’s getting long and complicated and just seems like there should be a better (simpler) way.
I keep wondering if I’m missing something in the basic aggregate DSL syntax.
I can paste in my calculation if anyone wants to see it but at the moment it’s a mess. Hoping someone just points me at a better solution.
Without more context, it’s little hard to understand how you would expect to sum :time. It represents a fixed point in time, whereas :duration is an abstract amount of time.
Time more directly maps to what I’m measuring, which is an activity (period of work) represented as hours and minutes (HH:MM). That makes it very easy to render in the UX, which is (basically) a HH : MM “spinner” control. Plus, :duration Ash handling is a bit opaque but, it seems to wrap seconds (which would be harder for me to work with, since I’m thinking in “hours & minutes”) and also :time, which ends up being an unnecessary abstraction… Maybe I don’t understand :duration well enough.
The error was a silly mistake. The calculation was returning a single value – not a list containing the result. Oops. So that solved the calculation.
As for the other problem… After going down the rabbit hole a bit, I realized I have all the Activities in memory. Why not just reduce in the UX (silly to use Ash if they are all in memory, I guess)… So, I ended up with:
def sum_activity_duration(activities) do
activities
|> Enum.reduce(~T[00:00:00], fn activity, acc ->
seconds = activity.duration.hour * 3600 + activity.duration.minute * 60 + activity.duration.second
# Add total seconds to the accumulator for display in UX `Time` control (HH:MM)
Time.add(acc, seconds, :second)
end)
end
I may have to take a look at Duration. But since the UX thinks in “HH:MM” and works nicely with Time I’m not sure it would be a win.