Aggregates with filter arguments

In my system, I have the following resources:

defmodule Core.Feedbacks.Student do
  relationships do
    alias Core.Feedbacks

    has_many :feedbacks, Feedbacks.Feedback

    many_to_many :classes, Feedbacks.Class do
      through Feedbacks.StudentClass
      join_relationship :student_classes

      source_attribute_on_join_resource :student_id
      destination_attribute_on_join_resource :class_id
    end
  end
end

defmodule Core.Feedbacks.Class do
  relationships do
    alias Core.Feedbacks

    has_many :feedbacks, Feedbacks.Feedback

    many_to_many :students, Feedbacks.Student do
      through Feedbacks.StudentClass

      source_attribute_on_join_resource :class_id
      destination_attribute_on_join_resource :student_id
    end
  end
end

defmodule Core.Feedbacks.StudentClass do
  relationships do
    alias Core.Feedbacks.{Student, Class}

    belongs_to :student, Student do
      primary_key? true
      allow_nil? false
      attribute_writable? true
    end

    belongs_to :class, Class do
      primary_key? true
      allow_nil? false
      attribute_writable? true
    end
  end
end

defmodule Core.Feedbacks.Feedback do
  relationships do
    alias Core.Feedbacks

    belongs_to :student, Feedbacks.Student do
      allow_nil? false
      attribute_writable? true
    end

    belongs_to :class, Feedbacks.Class do
      allow_nil? false
      attribute_writable? true
    end
  end
end

As you can see, the Student resource has a many_to_many relationship with the Class resource and both resources have a has_many relationship with the Feedback resource.

I wan´t to create an aggregate that will tell me how many feedbacks a student has inside a class.

If I just wanted all the student feedbacks, an aggregate like this in my Student resource would work just fine:

count :total_feedbacks, :feedbacks

But I want to count all the feedbacks that are from that student and from an specific class, meaning that I would to, somehow, pass an :class_id argument to inside that count aggregate filter expression.

I’m not sure how to solve that using aggregates

You can’t do that with standard aggregates, but you can do that with the new(-ish) inline aggregates syntax.

calculate :total_feedbacks, :integer, expr(
  count(feedbacks, query: [filter: expr(class_id == ^arg(:class_id))])
) do
  argument :class_id, :uuid, allow_nil?: false
end

As always, worked like a charm! :smile:

Hey @zachdaniel, I have another case where I’m not 100% sure how to solve it.

The calculations with inline aggregates worked great, but what if I want to aggregate something from one of the relationships?

For example.

Let’s say that the Class resource has a has_many relationship with another resource called Lessons.

I want to have a calculation in my Student resource that would give me number of lessons inside that class that a student is in.

What I tried to do was to first create an aggregate in the Class resource like this:

  aggregates do
    count :total_lessons, :lessons
  end

And then, inside the Student resource, I tried to create a calculation using that:

    calculate :total_lessons_in_class,
              :integer,
              expr(first(classes, query: [filter: expr(class_id == ^arg(:class_id))]).total_lessons) do
      argument :class_id, :string, allow_nil?: false
    end

But that doesn’t work.

I also tried adding a calculation that would return the current class and then I would be able to get the total_lessons field directly from it:

  calculate :current_class, :struct, expr(first(classes, query: [filter: expr(id == ^arg(:class_id))])) do
      constraints [instance_of: Core.Feedbacks.Class]

      argument :class_id, :string, allow_nil?: false
    end

But using the first aggregate doesn´t seem to be working, not sure if I’m doing something wrong with it.

So, what I’m about to show you wont’ work, but what you would want is this:

    calculate :total_lessons_in_class,
              :integer,
              expr(first(classes, query: [filter: expr(class_id == ^arg(:class_id))], field: :total_lessons)) do
      argument :class_id, :string, allow_nil?: false
    end

However, you can’t use an aggregate as the field of another aggregate. However, you can do this:

    calculate :total_lessons_in_class,
              :integer,
              expr(count(classes.lessons, query: [filter: expr(class_id == ^arg(:class_id))])) do
      argument :class_id, :string, allow_nil?: false
    end

Assuming that lesson has class_id on it.

Yep, this worked great, thanks again!