From a5d3062654bf69098c55e46121ed622d956c19cd Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Mon, 27 Jan 2025 20:08:28 +0100 Subject: [PATCH 1/5] Define and seed an invitation model --- app/models/expense.rb | 2 +- app/models/group.rb | 2 +- app/models/guest.rb | 26 +++++++++++-------- app/models/invitation.rb | 23 ++++++++++++++++ app/models/seat.rb | 2 +- app/models/tables_arrangement.rb | 2 +- app/models/user.rb | 2 +- config/initializers/acts_as_tenant.rb | 6 ++++- .../20250127183547_create_invitations.rb | 10 +++++++ ...27190131_fix_cascading_wedding_deletion.rb | 8 ++++++ db/schema.rb | 25 +++++++++++++----- db/seeds.rb | 8 ++---- spec/factories/invitations.rb | 7 +++++ spec/models/invitation_spec.rb | 7 +++++ 14 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 app/models/invitation.rb create mode 100644 db/migrate/20250127183547_create_invitations.rb create mode 100644 db/migrate/20250127190131_fix_cascading_wedding_deletion.rb create mode 100644 spec/factories/invitations.rb create mode 100644 spec/models/invitation_spec.rb diff --git a/app/models/expense.rb b/app/models/expense.rb index 66b32f3..a04a5c1 100644 --- a/app/models/expense.rb +++ b/app/models/expense.rb @@ -20,7 +20,7 @@ # # Foreign Keys # -# fk_rails_... (wedding_id => weddings.id) +# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # class Expense < ApplicationRecord acts_as_tenant :wedding diff --git a/app/models/group.rb b/app/models/group.rb index 2db28ed..e0db499 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -25,7 +25,7 @@ # Foreign Keys # # fk_rails_... (parent_id => groups.id) -# fk_rails_... (wedding_id => weddings.id) +# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # class Group < ApplicationRecord acts_as_tenant :wedding diff --git a/app/models/guest.rb b/app/models/guest.rb index d080679..54142ee 100644 --- a/app/models/guest.rb +++ b/app/models/guest.rb @@ -6,28 +6,32 @@ # # 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 -# wedding_id :uuid not null +# 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 +# invitation_id :uuid +# wedding_id :uuid not null # # Indexes # -# index_guests_on_group_id (group_id) -# index_guests_on_wedding_id (wedding_id) +# index_guests_on_group_id (group_id) +# index_guests_on_invitation_id (invitation_id) +# index_guests_on_wedding_id (wedding_id) # # Foreign Keys # # fk_rails_... (group_id => groups.id) -# fk_rails_... (wedding_id => weddings.id) +# fk_rails_... (invitation_id => invitations.id) +# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # class Guest < ApplicationRecord acts_as_tenant :wedding belongs_to :group, optional: true + belongs_to :invitation, optional: true enum :status, { considered: 0, diff --git a/app/models/invitation.rb b/app/models/invitation.rb new file mode 100644 index 0000000..8fb8c1a --- /dev/null +++ b/app/models/invitation.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: invitations +# +# id :uuid not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# wedding_id :uuid not null +# +# Indexes +# +# index_invitations_on_wedding_id (wedding_id) +# +# Foreign Keys +# +# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade +# +class Invitation < ApplicationRecord + acts_as_tenant :wedding + has_many :guests, dependent: :nullify +end diff --git a/app/models/seat.rb b/app/models/seat.rb index 9c31c42..f4f7b97 100644 --- a/app/models/seat.rb +++ b/app/models/seat.rb @@ -24,7 +24,7 @@ # # fk_rails_... (guest_id => guests.id) # fk_rails_... (tables_arrangement_id => tables_arrangements.id) ON DELETE => cascade -# fk_rails_... (wedding_id => weddings.id) +# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # class Seat < ApplicationRecord acts_as_tenant :wedding diff --git a/app/models/tables_arrangement.rb b/app/models/tables_arrangement.rb index a461555..b5fccb0 100644 --- a/app/models/tables_arrangement.rb +++ b/app/models/tables_arrangement.rb @@ -20,7 +20,7 @@ # # Foreign Keys # -# fk_rails_... (wedding_id => weddings.id) +# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # class TablesArrangement < ApplicationRecord acts_as_tenant :wedding diff --git a/app/models/user.rb b/app/models/user.rb index ff46fff..6c7d79c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -32,7 +32,7 @@ # # Foreign Keys # -# fk_rails_... (wedding_id => weddings.id) +# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # class User < ApplicationRecord acts_as_tenant :wedding diff --git a/config/initializers/acts_as_tenant.rb b/config/initializers/acts_as_tenant.rb index fd8890d..25855eb 100644 --- a/config/initializers/acts_as_tenant.rb +++ b/config/initializers/acts_as_tenant.rb @@ -2,4 +2,8 @@ ActsAsTenant.configure do |config| config.require_tenant = !Rails.env.test? -end \ No newline at end of file +end + +Rails.application.console do + ActsAsTenant.current_tenant = Wedding.first +end diff --git a/db/migrate/20250127183547_create_invitations.rb b/db/migrate/20250127183547_create_invitations.rb new file mode 100644 index 0000000..ed2e8a3 --- /dev/null +++ b/db/migrate/20250127183547_create_invitations.rb @@ -0,0 +1,10 @@ +class CreateInvitations < ActiveRecord::Migration[8.0] + def change + create_table :invitations, id: :uuid do |t| + t.references :wedding, null: false, foreign_key: { on_delete: :cascade }, type: :uuid + t.timestamps + end + + add_reference :guests, :invitation, foreign_key: true, type: :uuid + end +end diff --git a/db/migrate/20250127190131_fix_cascading_wedding_deletion.rb b/db/migrate/20250127190131_fix_cascading_wedding_deletion.rb new file mode 100644 index 0000000..366df85 --- /dev/null +++ b/db/migrate/20250127190131_fix_cascading_wedding_deletion.rb @@ -0,0 +1,8 @@ +class FixCascadingWeddingDeletion < ActiveRecord::Migration[8.0] + def change + [:expenses, :groups, :guests, :seats, :tables_arrangements, :users].each do |table| + remove_foreign_key table, column: :wedding_id + add_foreign_key table, :weddings, on_delete: :cascade + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 5b61f3f..5a7e2fd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_01_26_091823) do +ActiveRecord::Schema[8.0].define(version: 2025_01_27_190131) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -63,10 +63,19 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_26_091823) do t.integer "status", default: 0 t.string "name" t.uuid "wedding_id", null: false + t.uuid "invitation_id" t.index ["group_id"], name: "index_guests_on_group_id" + t.index ["invitation_id"], name: "index_guests_on_invitation_id" t.index ["wedding_id"], name: "index_guests_on_wedding_id" end + create_table "invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "wedding_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["wedding_id"], name: "index_invitations_on_wedding_id" + end + create_table "seats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "guest_id", null: false t.uuid "tables_arrangement_id", null: false @@ -239,22 +248,24 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_26_091823) do t.index ["slug"], name: "index_weddings_on_slug", unique: true end - add_foreign_key "expenses", "weddings" + add_foreign_key "expenses", "weddings", on_delete: :cascade add_foreign_key "group_affinities", "groups", column: "group_a_id" add_foreign_key "group_affinities", "groups", column: "group_b_id" add_foreign_key "groups", "groups", column: "parent_id" - add_foreign_key "groups", "weddings" + add_foreign_key "groups", "weddings", on_delete: :cascade add_foreign_key "guests", "groups" - add_foreign_key "guests", "weddings" + add_foreign_key "guests", "invitations" + add_foreign_key "guests", "weddings", on_delete: :cascade + add_foreign_key "invitations", "weddings", on_delete: :cascade add_foreign_key "seats", "guests" add_foreign_key "seats", "tables_arrangements", on_delete: :cascade - add_foreign_key "seats", "weddings" + add_foreign_key "seats", "weddings", on_delete: :cascade add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade - add_foreign_key "tables_arrangements", "weddings" - add_foreign_key "users", "weddings" + add_foreign_key "tables_arrangements", "weddings", on_delete: :cascade + add_foreign_key "users", "weddings", on_delete: :cascade end diff --git a/db/seeds.rb b/db/seeds.rb index fcf5ec3..e7169ab 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -3,11 +3,6 @@ NUMBER_OF_GUESTS = 50 ActsAsTenant.without_tenant do - TablesArrangement.delete_all - Expense.delete_all - Guest.delete_all - Group.delete_all - Wedding.delete_all end @@ -66,7 +61,8 @@ ActsAsTenant.with_tenant(wedding) do name: Faker::Name.name, phone: Faker::PhoneNumber.cell_phone, group: groups.sample, - status: Guest.statuses.keys.sample + status: Guest.statuses.keys.sample, + invitation: (Invitation.create!(wedding:) if rand > 0.1), ) end diff --git a/spec/factories/invitations.rb b/spec/factories/invitations.rb new file mode 100644 index 0000000..b4ba145 --- /dev/null +++ b/spec/factories/invitations.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :invitation do + wedding + end +end diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb new file mode 100644 index 0000000..24c963e --- /dev/null +++ b/spec/models/invitation_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Invitation do + pending "add some examples to (or delete) #{__FILE__}" +end -- 2.47.1 From b112aefe215a6b60d1d24083202c2206f1c2b5f1 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Mon, 27 Jan 2025 20:22:21 +0100 Subject: [PATCH 2/5] Optimize seeds file --- db/seeds.rb | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index e7169ab..a900a1e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (C) 2024-2025 LibreWeddingPlanner contributors NUMBER_OF_GUESTS = 50 @@ -9,20 +11,22 @@ end wedding = Wedding.create!(slug: :default) ActsAsTenant.with_tenant(wedding) do - Expense.create!(name: 'Photographer', amount: 3000, pricing_type: 'fixed') - Expense.create!(name: 'Country house', amount: 6000, pricing_type: 'fixed') - Expense.create!(name: 'Catering', amount: 200, pricing_type: 'per_person') - Expense.create!(name: 'Flowers', amount: 500, pricing_type: 'fixed') - Expense.create!(name: 'Band', amount: 1000, pricing_type: 'fixed') - Expense.create!(name: 'Wedding planner', amount: 2000, pricing_type: 'fixed') - Expense.create!(name: 'Dress', amount: 1000, pricing_type: 'fixed') - Expense.create!(name: 'Suit', amount: 500, pricing_type: 'fixed') - Expense.create!(name: 'Rings', amount: 1000, pricing_type: 'fixed') - Expense.create!(name: 'Makeup', amount: 200, pricing_type: 'fixed') - Expense.create!(name: 'Hair', amount: 200, pricing_type: 'fixed') - Expense.create!(name: 'Transportation', amount: 3000, pricing_type: 'fixed') - Expense.create!(name: 'Invitations', amount: 200, pricing_type: 'fixed') - Expense.create!(name: 'Cake', amount: 500, pricing_type: 'fixed') + [ + { name: 'Photographer', amount: 3000, pricing_type: 'fixed' }, + { name: 'Country house', amount: 6000, pricing_type: 'fixed' }, + { name: 'Catering', amount: 200, pricing_type: 'per_person' }, + { name: 'Flowers', amount: 500, pricing_type: 'fixed' }, + { name: 'Band', amount: 1000, pricing_type: 'fixed' }, + { name: 'Wedding planner', amount: 2000, pricing_type: 'fixed' }, + { name: 'Dress', amount: 1000, pricing_type: 'fixed' }, + { name: 'Suit', amount: 500, pricing_type: 'fixed' }, + { name: 'Rings', amount: 1000, pricing_type: 'fixed' }, + { name: 'Makeup', amount: 200, pricing_type: 'fixed' }, + { name: 'Hair', amount: 200, pricing_type: 'fixed' }, + { name: 'Transportation', amount: 3000, pricing_type: 'fixed' }, + { name: 'Invitations', amount: 200, pricing_type: 'fixed' }, + { name: 'Cake', amount: 500, pricing_type: 'fixed' } + ].then { Expense.insert_all!(it) } Group.create!(name: "Jim's guests", icon: 'pi pi-heart').tap do |parent| parent.children.create!(name: "Jim's family", icon: 'pi pi-users').tap do |family| @@ -56,19 +60,21 @@ ActsAsTenant.with_tenant(wedding) do groups = Group.all - NUMBER_OF_GUESTS.times do - Guest.create!( + invitations = ([{}] * (NUMBER_OF_GUESTS * 0.8)).then { Invitation.insert_all!(it) }.rows.flatten + + NUMBER_OF_GUESTS.times.map do |i| + { name: Faker::Name.name, phone: Faker::PhoneNumber.cell_phone, - group: groups.sample, + group_id: groups.sample.id, status: Guest.statuses.keys.sample, - invitation: (Invitation.create!(wedding:) if rand > 0.1), - ) - end + invitation_id: invitations[i] + } + end.then { Guest.insert_all!(it) } ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new(wedding.id) }) - 'red'.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } + "red".dup.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } Group.roots.each(&:colorize_children) @@ -76,6 +82,6 @@ ActsAsTenant.with_tenant(wedding) do email: 'development@example.com', confirmed_at: Time.zone.now, password: 'supersecretpassword', - password_confirmation: 'supersecretpassword', + password_confirmation: 'supersecretpassword' ) end -- 2.47.1 From ecbb6af4bd49824bed74784ebcc689b105b0be49 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Mon, 27 Jan 2025 20:42:31 +0100 Subject: [PATCH 3/5] Increase seeds size --- db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index a900a1e..79afb17 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -2,7 +2,7 @@ # Copyright (C) 2024-2025 LibreWeddingPlanner contributors -NUMBER_OF_GUESTS = 50 +NUMBER_OF_GUESTS = 200 ActsAsTenant.without_tenant do Wedding.delete_all -- 2.47.1 From 40f89ae179b8852d6f4d9e3df2eb4d4b102c6e07 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Mon, 27 Jan 2025 19:44:30 +0000 Subject: [PATCH 4/5] Add copyright notice --- app/models/invitation.rb | 2 ++ spec/factories/invitations.rb | 2 ++ spec/models/invitation_spec.rb | 2 ++ 3 files changed, 6 insertions(+) diff --git a/app/models/invitation.rb b/app/models/invitation.rb index 8fb8c1a..020242e 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024-2025 LibreWeddingPlanner contributors + # frozen_string_literal: true # == Schema Information diff --git a/spec/factories/invitations.rb b/spec/factories/invitations.rb index b4ba145..a1dab61 100644 --- a/spec/factories/invitations.rb +++ b/spec/factories/invitations.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024-2025 LibreWeddingPlanner contributors + # frozen_string_literal: true FactoryBot.define do diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb index 24c963e..f4cc2c3 100644 --- a/spec/models/invitation_spec.rb +++ b/spec/models/invitation_spec.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024-2025 LibreWeddingPlanner contributors + # frozen_string_literal: true require 'rails_helper' -- 2.47.1 From 522bcb0032816bdcd57d5a12d236d2334670ca49 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Tue, 28 Jan 2025 08:59:15 +0100 Subject: [PATCH 5/5] Generate invitations within the same group --- app/controllers/guests_controller.rb | 2 +- db/seeds.rb | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/controllers/guests_controller.rb b/app/controllers/guests_controller.rb index 1d872fa..78cd2a6 100644 --- a/app/controllers/guests_controller.rb +++ b/app/controllers/guests_controller.rb @@ -9,7 +9,7 @@ class GuestsController < ApplicationController render json: Guest.includes(:group) .left_joins(:group) .order('groups.name' => :asc, name: :asc) - .as_json(only: %i[id name status], include: { group: { only: %i[id name] } }) + .as_json(only: %i[id name status invitation_id], include: { group: { only: %i[id name] } }) end def create diff --git a/db/seeds.rb b/db/seeds.rb index 79afb17..a693f64 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -60,18 +60,29 @@ ActsAsTenant.with_tenant(wedding) do groups = Group.all - invitations = ([{}] * (NUMBER_OF_GUESTS * 0.8)).then { Invitation.insert_all!(it) }.rows.flatten - NUMBER_OF_GUESTS.times.map do |i| { name: Faker::Name.name, phone: Faker::PhoneNumber.cell_phone, group_id: groups.sample.id, status: Guest.statuses.keys.sample, - invitation_id: invitations[i] } end.then { Guest.insert_all!(it) } + Group.includes(:guests).each do |group| + guests = group.guests.potential.to_a + + while guests.any? + invitation = Invitation.create! + + guests.shift(rand(1..3)).each do |guest| + guest.update!(invitation:) + end + end + end + + # TODO: Clean up invitations with no guests + ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new(wedding.id) }) "red".dup.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } -- 2.47.1