Merge pull request 'Introduce endpoint to retrieve a summary of groups and invite attendance' (#122) from groups-index-stats into main
Reviewed-on: #122
This commit is contained in:
commit
d75b117c60
58
.annotaterb.yml
Normal file
58
.annotaterb.yml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
:position: before
|
||||||
|
:position_in_additional_file_patterns: before
|
||||||
|
:position_in_class: before
|
||||||
|
:position_in_factory: before
|
||||||
|
:position_in_fixture: before
|
||||||
|
:position_in_routes: before
|
||||||
|
:position_in_serializer: before
|
||||||
|
:position_in_test: before
|
||||||
|
:classified_sort: true
|
||||||
|
:exclude_controllers: true
|
||||||
|
:exclude_factories: true
|
||||||
|
:exclude_fixtures: false
|
||||||
|
:exclude_helpers: true
|
||||||
|
:exclude_scaffolds: true
|
||||||
|
:exclude_serializers: false
|
||||||
|
:exclude_sti_subclasses: false
|
||||||
|
:exclude_tests: true
|
||||||
|
:force: false
|
||||||
|
:format_markdown: false
|
||||||
|
:format_rdoc: false
|
||||||
|
:format_yard: false
|
||||||
|
:frozen: false
|
||||||
|
:ignore_model_sub_dir: false
|
||||||
|
:ignore_unknown_models: false
|
||||||
|
:include_version: false
|
||||||
|
:show_check_constraints: false
|
||||||
|
:show_complete_foreign_keys: false
|
||||||
|
:show_foreign_keys: true
|
||||||
|
:show_indexes: true
|
||||||
|
:simple_indexes: false
|
||||||
|
:sort: false
|
||||||
|
:timestamp: false
|
||||||
|
:trace: false
|
||||||
|
:with_comment: true
|
||||||
|
:with_column_comments: true
|
||||||
|
:with_table_comments: true
|
||||||
|
:active_admin: false
|
||||||
|
:command:
|
||||||
|
:debug: false
|
||||||
|
:hide_default_column_types: ''
|
||||||
|
:hide_limit_column_types: ''
|
||||||
|
:ignore_columns:
|
||||||
|
:ignore_routes:
|
||||||
|
:models: true
|
||||||
|
:routes: false
|
||||||
|
:skip_on_db_migrate: false
|
||||||
|
:target_action: :do_annotations
|
||||||
|
:wrapper:
|
||||||
|
:wrapper_close:
|
||||||
|
:wrapper_open:
|
||||||
|
:classes_default_to_s: []
|
||||||
|
:additional_file_patterns: []
|
||||||
|
:model_dir:
|
||||||
|
- app/models
|
||||||
|
:require: []
|
||||||
|
:root_dir:
|
||||||
|
- ''
|
1
Gemfile
1
Gemfile
@ -22,6 +22,7 @@ gem 'react-rails'
|
|||||||
gem 'rubytree'
|
gem 'rubytree'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
gem 'annotaterb'
|
||||||
gem 'debug', platforms: %i[mri windows]
|
gem 'debug', platforms: %i[mri windows]
|
||||||
gem 'factory_bot_rails'
|
gem 'factory_bot_rails'
|
||||||
gem 'license_finder'
|
gem 'license_finder'
|
||||||
|
@ -72,6 +72,7 @@ GEM
|
|||||||
securerandom (>= 0.3)
|
securerandom (>= 0.3)
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
uri (>= 0.13.1)
|
uri (>= 0.13.1)
|
||||||
|
annotaterb (4.13.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
babel-source (5.8.35)
|
babel-source (5.8.35)
|
||||||
babel-transpiler (0.7.0)
|
babel-transpiler (0.7.0)
|
||||||
@ -341,6 +342,7 @@ PLATFORMS
|
|||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
annotaterb
|
||||||
bootsnap
|
bootsnap
|
||||||
chroma
|
chroma
|
||||||
csv
|
csv
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
class GroupsController < ApplicationController
|
class GroupsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
roots = Group.where(parent_id: nil)
|
render json: Groups::SummaryQuery.new.call.as_json
|
||||||
render jsonapi: roots, include: [children: [children: [:children]]]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024 Manuel Bustillo
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: expenses
|
||||||
|
#
|
||||||
|
# id :uuid not null, primary key
|
||||||
|
# amount :decimal(, )
|
||||||
|
# name :string
|
||||||
|
# pricing_type :enum default("fixed"), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
class Expense < ApplicationRecord
|
class Expense < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,27 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024 Manuel Bustillo
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: groups
|
||||||
|
#
|
||||||
|
# id :uuid not null, primary key
|
||||||
|
# color :string
|
||||||
|
# icon :string
|
||||||
|
# name :string not null
|
||||||
|
# order :integer default(1), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# parent_id :uuid
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_groups_on_name (name) UNIQUE
|
||||||
|
# index_groups_on_parent_id (parent_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (parent_id => groups.id)
|
||||||
|
#
|
||||||
class Group < ApplicationRecord
|
class Group < ApplicationRecord
|
||||||
validates :name, uniqueness: true
|
validates :name, uniqueness: true
|
||||||
validates :name, :order, presence: true
|
validates :name, :order, presence: true
|
||||||
@ -30,6 +52,8 @@ class Group < ApplicationRecord
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_color
|
def set_color
|
||||||
|
return if color.present?
|
||||||
|
|
||||||
new_color = "##{SecureRandom.hex(3)}".paint
|
new_color = "##{SecureRandom.hex(3)}".paint
|
||||||
new_color = new_color.lighten(30) if new_color.dark?
|
new_color = new_color.lighten(30) if new_color.dark?
|
||||||
self.color = new_color
|
self.color = new_color
|
||||||
|
@ -1,5 +1,25 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024 Manuel Bustillo
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: guests
|
||||||
|
#
|
||||||
|
# id :uuid not null, primary key
|
||||||
|
# name :string
|
||||||
|
# phone :string
|
||||||
|
# status :integer default("considered")
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# group_id :uuid not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_guests_on_group_id (group_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (group_id => groups.id)
|
||||||
|
#
|
||||||
class Guest < ApplicationRecord
|
class Guest < ApplicationRecord
|
||||||
belongs_to :group
|
belongs_to :group
|
||||||
|
|
||||||
|
@ -1,5 +1,26 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024 Manuel Bustillo
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: seats
|
||||||
|
#
|
||||||
|
# id :uuid not null, primary key
|
||||||
|
# table_number :integer
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# guest_id :uuid not null
|
||||||
|
# tables_arrangement_id :uuid not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_seats_on_guest_id (guest_id)
|
||||||
|
# index_seats_on_tables_arrangement_id (tables_arrangement_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (guest_id => guests.id)
|
||||||
|
# fk_rails_... (tables_arrangement_id => tables_arrangements.id) ON DELETE => cascade
|
||||||
|
#
|
||||||
class Seat < ApplicationRecord
|
class Seat < ApplicationRecord
|
||||||
belongs_to :guest
|
belongs_to :guest
|
||||||
belongs_to :table_arrangement
|
belongs_to :table_arrangement
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024 Manuel Bustillo
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: tables_arrangements
|
||||||
|
#
|
||||||
|
# id :uuid not null, primary key
|
||||||
|
# discomfort :integer
|
||||||
|
# name :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
class TablesArrangement < ApplicationRecord
|
class TablesArrangement < ApplicationRecord
|
||||||
has_many :seats
|
has_many :seats
|
||||||
has_many :guests, through: :seats
|
has_many :guests, through: :seats
|
||||||
|
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
|
2
db/schema.rb
generated
2
db/schema.rb
generated
@ -1,5 +1,3 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
|
||||||
|
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
# This file is auto-generated from the current state of the database. Instead
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
# of editing this file, please use the migrations feature of Active Record to
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
# incrementally modify your database, and then regenerate this schema definition.
|
||||||
|
8
lib/tasks/annotate_rb.rake
Normal file
8
lib/tasks/annotate_rb.rake
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# This rake task was added by annotate_rb gem.
|
||||||
|
|
||||||
|
# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
|
||||||
|
if Rails.env.development? && ENV["ANNOTATERB_SKIP_ON_DB_TASKS"].nil?
|
||||||
|
require "annotate_rb"
|
||||||
|
|
||||||
|
AnnotateRb::Core.load_rake_tasks
|
||||||
|
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