diff --git a/.annotaterb.yml b/.annotaterb.yml new file mode 100644 index 0000000..5c007f0 --- /dev/null +++ b/.annotaterb.yml @@ -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: +- '' diff --git a/Gemfile b/Gemfile index c64df42..83b5acf 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ gem 'react-rails' gem 'rubytree' group :development, :test do + gem 'annotaterb' gem 'debug', platforms: %i[mri windows] gem 'factory_bot_rails' gem 'license_finder' diff --git a/Gemfile.lock b/Gemfile.lock index fa7df0e..3d76af1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,7 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) + annotaterb (4.13.0) ast (2.4.2) babel-source (5.8.35) babel-transpiler (0.7.0) @@ -341,6 +342,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + annotaterb bootsnap chroma csv diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 232ac85..37df884 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -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 diff --git a/app/models/expense.rb b/app/models/expense.rb index 34e2e33..32f486f 100644 --- a/app/models/expense.rb +++ b/app/models/expense.rb @@ -1,4 +1,15 @@ # 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 end diff --git a/app/models/group.rb b/app/models/group.rb index 105d08c..fe11e0b 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,5 +1,27 @@ # 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 validates :name, uniqueness: true validates :name, :order, presence: true @@ -30,6 +52,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 diff --git a/app/models/guest.rb b/app/models/guest.rb index bfa1265..6b8f70e 100644 --- a/app/models/guest.rb +++ b/app/models/guest.rb @@ -1,5 +1,25 @@ # 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 belongs_to :group diff --git a/app/models/seat.rb b/app/models/seat.rb index 8395b9c..1f3769c 100644 --- a/app/models/seat.rb +++ b/app/models/seat.rb @@ -1,5 +1,26 @@ # 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 belongs_to :guest belongs_to :table_arrangement diff --git a/app/models/tables_arrangement.rb b/app/models/tables_arrangement.rb index d1eba03..6e2f621 100644 --- a/app/models/tables_arrangement.rb +++ b/app/models/tables_arrangement.rb @@ -1,5 +1,15 @@ # 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 has_many :seats has_many :guests, through: :seats diff --git a/app/queries/groups/summary_query.rb b/app/queries/groups/summary_query.rb new file mode 100644 index 0000000..637a508 --- /dev/null +++ b/app/queries/groups/summary_query.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index c54ec05..7107478 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,5 +1,3 @@ -# Copyright (C) 2024 Manuel Bustillo - # 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 # incrementally modify your database, and then regenerate this schema definition. diff --git a/lib/tasks/annotate_rb.rake b/lib/tasks/annotate_rb.rake new file mode 100644 index 0000000..1ad0ec3 --- /dev/null +++ b/lib/tasks/annotate_rb.rake @@ -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 diff --git a/spec/queries/groups/summary_query_spec.rb b/spec/queries/groups/summary_query_spec.rb new file mode 100644 index 0000000..6e2c3d2 --- /dev/null +++ b/spec/queries/groups/summary_query_spec.rb @@ -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