Redo simulations lifecycle (#222)
All checks were successful
Run unit tests / rubocop (push) Successful in 27s
Run unit tests / check-licenses (push) Successful in 32s
Run unit tests / copyright_notice (push) Successful in 36s
Run unit tests / unit_tests (push) Successful in 1m22s
Run unit tests / build-static-assets (push) Successful in 10m9s

## Why

The current way of creating and deleting simulations doesn't scale for big instances. We cannot generate 50 simulations every time a guest confirms attendance, and we should not delete existing valuable simulations. For example, if a guest confirms attendance and declines right after, previously generated simulations should still be valid.

## What

In this PR we are introducing a series of changes that make simulations management easier:

1. We're removing the automatic creation of simulations.
2. Simulations are not removed anymore, neither manually nor automatically.
3. A new endpoint has been defined to create simulations on demand.
4. A digest property has been defined to determine whether a simulation is still valid (meaning there have not been any change in the list of guests involved).

Reviewed-on: #222
Co-authored-by: Manuel Bustillo <bustikiller@bustikiller.com>
Co-committed-by: Manuel Bustillo <bustikiller@bustikiller.com>
This commit is contained in:
Manuel Bustillo 2025-01-26 12:53:21 +00:00 committed by bustikiller
parent f414acb2d5
commit 2147d7ad5e
9 changed files with 52 additions and 17 deletions

View File

@ -4,7 +4,15 @@
class TablesArrangementsController < ApplicationController
def index
render json: TablesArrangement.order(discomfort: :asc).limit(3).as_json(only: %i[id name discomfort])
current_digest = Tables::Distribution.digest(current_tenant)
render json: TablesArrangement
.order(valid: :desc)
.order(discomfort: :asc)
.select(:id, :name, :discomfort)
.select("digest = '#{current_digest}'::uuid as valid")
.limit(20)
.as_json(only: %i[id name discomfort valid])
end
def show
@ -16,6 +24,12 @@ class TablesArrangementsController < ApplicationController
.then { |result| render json: { id: params[:id], tables: result } }
end
def create
TableSimulatorJob.perform_later(current_tenant.id)
render json: {}, status: :created
end
private
def format(number:, guests:)

View File

@ -41,16 +41,5 @@ class Guest < ApplicationRecord
scope :potential, -> { where.not(status: %i[declined considered]) }
after_destroy :recalculate_simulations
after_save :recalculate_simulations, if: :saved_change_to_status?
has_many :seats, dependent: :delete_all
private
def recalculate_simulations
TablesArrangement.delete_all
ActiveJob.perform_all_later(50.times.map { TableSimulatorJob.new(wedding_id) })
end
end

View File

@ -7,6 +7,7 @@
# Table name: tables_arrangements
#
# id :uuid not null, primary key
# digest :uuid not null
# discomfort :integer
# name :string not null
# created_at :datetime not null

View File

@ -19,4 +19,6 @@ class Wedding < ApplicationRecord
SLUG_REGEX = /[a-z\d-]+/
validates :slug, presence: true, uniqueness: true, format: { with: /\A#{SLUG_REGEX}\z/ }
has_many :guests, dependent: :delete_all
end

View File

@ -6,7 +6,13 @@ require_relative '../../extensions/tree_node_extension'
module Tables
class Distribution
attr_accessor :tables, :min_per_table, :max_per_table
class << self
def digest(wedding)
Digest::UUID.uuid_v5(wedding.id, wedding.guests.potential.order(:id).pluck(:id).join)
end
end
attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy
def initialize(min_per_table:, max_per_table:)
@min_per_table = min_per_table
@ -54,7 +60,10 @@ module Tables
Seat.insert_all!(records_to_store)
arrangement.update!(discomfort:)
arrangement.update!(
discomfort:,
digest: self.class.digest(tables.first.first.wedding)
)
end
end

View File

@ -37,7 +37,7 @@ Rails.application.routes.draw do
resources :expenses, only: %i[index create update destroy] do
get :summary, on: :collection
end
resources :tables_arrangements, only: %i[index show]
resources :tables_arrangements, only: %i[index show create]
resources :summary, only: :index
root to: redirect("/%{slug}")

View File

@ -0,0 +1,5 @@
class AddGuestsDigestColumnToTablesArrangements < ActiveRecord::Migration[8.0]
def change
add_column :tables_arrangements, :digest, :uuid, null: false, default: 'gen_random_uuid()'
end
end

3
db/schema.rb generated
View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2024_12_16_231415) do
ActiveRecord::Schema[8.0].define(version: 2025_01_26_091823) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -206,6 +206,7 @@ ActiveRecord::Schema[8.0].define(version: 2024_12_16_231415) do
t.datetime "updated_at", null: false
t.string "name", null: false
t.uuid "wedding_id", null: false
t.uuid "digest", default: -> { "gen_random_uuid()" }, null: false
t.index ["wedding_id"], name: "index_tables_arrangements_on_wedding_id"
end

View File

@ -18,13 +18,27 @@ RSpec.describe 'tables_arrangements' do
properties: {
id: { type: :string, format: :uuid },
name: { type: :string },
discomfort: { type: :integer }
discomfort: { type: :integer },
valid: { type: :boolean }
}
}
xit
end
regular_api_responses
end
post('create tables arrangement') do
tags 'Tables Arrangements'
produces 'application/json'
parameter Swagger::Schema::SLUG
response(201, 'successful') do
schema type: :object,
required: [],
properties: {}
xit
end
regular_api_responses
end
end
path '/{slug}/tables_arrangements/{id}' do