Introduce endpoint to retrieve a summary of groups and invite attendance #122
@ -2,7 +2,6 @@
|
||||
|
||||
class GroupsController < ApplicationController
|
||||
def index
|
||||
roots = Group.where(parent_id: nil)
|
||||
render jsonapi: roots, include: [children: [children: [:children]]]
|
||||
render json: Groups::SummaryQuery.new.call.as_json
|
||||
end
|
||||
end
|
||||
|
@ -30,6 +30,8 @@ class Group < ApplicationRecord
|
||||
private
|
||||
|
||||
def set_color
|
||||
return if color.present?
|
||||
|
||||
new_color = "##{SecureRandom.hex(3)}".paint
|
||||
new_color = new_color.lighten(30) if new_color.dark?
|
||||
self.color = new_color
|
||||
|
31
app/queries/groups/summary_query.rb
Normal file
31
app/queries/groups/summary_query.rb
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright (C) 2024 Manuel Bustillo
|
||||
|
||||
module Groups
|
||||
class SummaryQuery
|
||||
def call
|
||||
ActiveRecord::Base.connection.execute(query).to_a
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def query
|
||||
<<~SQL.squish
|
||||
SELECT#{' '}
|
||||
groups.id,
|
||||
groups.name,
|
||||
groups.icon,
|
||||
groups.parent_id,
|
||||
groups.color,
|
||||
count(*) filter (where status IS NOT NULL) as total,
|
||||
count(*) filter (where status = 0) as considered,
|
||||
count(*) filter (where status = 10) as invited,
|
||||
count(*) filter (where status = 20) as confirmed,
|
||||
count(*) filter (where status = 30) as declined,
|
||||
count(*) filter (where status = 40) as tentative
|
||||
FROM groups
|
||||
LEFT JOIN guests on groups.id = guests.group_id
|
||||
GROUP BY groups.id
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
98
spec/queries/groups/summary_query_spec.rb
Normal file
98
spec/queries/groups/summary_query_spec.rb
Normal file
@ -0,0 +1,98 @@
|
||||
# Copyright (C) 2024 Manuel Bustillo
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
module Groups
|
||||
RSpec.describe SummaryQuery do
|
||||
describe '#call' do
|
||||
subject { described_class.new.call }
|
||||
|
||||
context 'when there are no groups' do
|
||||
it { is_expected.to eq([]) }
|
||||
end
|
||||
|
||||
context 'when groups are defined' do
|
||||
let!(:parent) { create(:group, name: 'Friends', icon: 'icon-1', color: '#FF0000') }
|
||||
let!(:child) { create(:group, name: 'Family', icon: 'icon-2', color: '#00FF00', parent:) }
|
||||
|
||||
context 'when there are no guests' do
|
||||
it 'returns the summary of groups' do
|
||||
is_expected.to contain_exactly(
|
||||
{ 'id' => parent.id,
|
||||
'name' => 'Friends',
|
||||
'icon' => 'icon-1',
|
||||
'parent_id' => nil,
|
||||
'color' => '#FF0000',
|
||||
'total' => 0,
|
||||
'considered' => 0,
|
||||
'invited' => 0,
|
||||
'confirmed' => 0,
|
||||
'declined' => 0,
|
||||
'tentative' => 0 },
|
||||
{ 'id' => child.id,
|
||||
'name' => 'Family',
|
||||
'icon' => 'icon-2',
|
||||
'parent_id' => parent.id,
|
||||
'color' => '#00FF00',
|
||||
'total' => 0,
|
||||
'considered' => 0,
|
||||
'invited' => 0,
|
||||
'confirmed' => 0,
|
||||
'declined' => 0,
|
||||
'tentative' => 0 }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are guests' do
|
||||
before do
|
||||
# Parent group
|
||||
create_list(:guest, 2, group: parent, status: :considered)
|
||||
create_list(:guest, 3, group: parent, status: :invited)
|
||||
create_list(:guest, 4, group: parent, status: :confirmed)
|
||||
create_list(:guest, 5, group: parent, status: :declined)
|
||||
create_list(:guest, 6, group: parent, status: :tentative)
|
||||
|
||||
# Child group
|
||||
create_list(:guest, 7, group: child, status: :considered)
|
||||
create_list(:guest, 8, group: child, status: :invited)
|
||||
create_list(:guest, 9, group: child, status: :confirmed)
|
||||
create_list(:guest, 10, group: child, status: :declined)
|
||||
create_list(:guest, 11, group: child, status: :tentative)
|
||||
end
|
||||
|
||||
it 'returns the summary of groups' do
|
||||
is_expected.to contain_exactly(
|
||||
{
|
||||
'id' => parent.id,
|
||||
'name' => 'Friends',
|
||||
'icon' => 'icon-1',
|
||||
'parent_id' => nil,
|
||||
'color' => '#FF0000',
|
||||
'total' => 20,
|
||||
'considered' => 2,
|
||||
'invited' => 3,
|
||||
'confirmed' => 4,
|
||||
'declined' => 5,
|
||||
'tentative' => 6
|
||||
},
|
||||
{
|
||||
'id' => child.id,
|
||||
'name' => 'Family',
|
||||
'icon' => 'icon-2',
|
||||
'parent_id' => parent.id,
|
||||
'color' => '#00FF00',
|
||||
'total' => 45,
|
||||
'considered' => 7,
|
||||
'invited' => 8,
|
||||
'confirmed' => 9,
|
||||
'declined' => 10,
|
||||
'tentative' => 11
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user