From 24c39f331ae5d1346583b35eece943ead0392bc5 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 19:24:59 +0100 Subject: [PATCH 01/13] Define a simple wedding model --- app/models/wedding.rb | 18 ++++++++++++++++++ db/migrate/20241130182228_create_weddings.rb | 10 ++++++++++ db/schema.rb | 12 +++++++++--- db/seeds.rb | 4 ++++ spec/factories/weddings.rb | 5 +++++ spec/models/wedding_spec.rb | 5 +++++ 6 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 app/models/wedding.rb create mode 100644 db/migrate/20241130182228_create_weddings.rb create mode 100644 spec/factories/weddings.rb create mode 100644 spec/models/wedding_spec.rb diff --git a/app/models/wedding.rb b/app/models/wedding.rb new file mode 100644 index 0000000..32c5578 --- /dev/null +++ b/app/models/wedding.rb @@ -0,0 +1,18 @@ +# == Schema Information +# +# Table name: weddings +# +# id :uuid not null, primary key +# date :date not null +# slug :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_weddings_on_slug (slug) UNIQUE +# +class Wedding < ApplicationRecord + validates :date, presence: true + validates :slug, presence: true, uniqueness: true +end diff --git a/db/migrate/20241130182228_create_weddings.rb b/db/migrate/20241130182228_create_weddings.rb new file mode 100644 index 0000000..c8cf1fd --- /dev/null +++ b/db/migrate/20241130182228_create_weddings.rb @@ -0,0 +1,10 @@ +class CreateWeddings < ActiveRecord::Migration[8.0] + def change + create_table :weddings, id: :uuid do |t| + t.string :slug, null: false, index: { unique: true } + t.date :date, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6e9d747..41896ca 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. @@ -12,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2024_11_30_095753) do +ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -208,6 +206,14 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_095753) do t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true end + create_table "weddings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.string "slug", null: false + t.date "date", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["slug"], name: "index_weddings_on_slug", unique: true + end + add_foreign_key "groups", "groups", column: "parent_id" add_foreign_key "guests", "groups" add_foreign_key "seats", "guests" diff --git a/db/seeds.rb b/db/seeds.rb index 42c175e..e7e414d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,6 +7,10 @@ Expense.delete_all Guest.delete_all Group.delete_all +Wedding.delete_all + +Wedding.create!(date: 1.year.from_now) + 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') diff --git a/spec/factories/weddings.rb b/spec/factories/weddings.rb new file mode 100644 index 0000000..5528d3b --- /dev/null +++ b/spec/factories/weddings.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :wedding do + date { 1.year.from_now } + end +end diff --git a/spec/models/wedding_spec.rb b/spec/models/wedding_spec.rb new file mode 100644 index 0000000..8725ba8 --- /dev/null +++ b/spec/models/wedding_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Wedding, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From 9e222f59bed52a33794a8a30482242fc57cb1aa1 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 18:31:48 +0000 Subject: [PATCH 02/13] Add copyright notice --- app/models/wedding.rb | 2 ++ db/migrate/20241130182228_create_weddings.rb | 2 ++ db/schema.rb | 2 ++ spec/factories/weddings.rb | 2 ++ spec/models/wedding_spec.rb | 2 ++ 5 files changed, 10 insertions(+) diff --git a/app/models/wedding.rb b/app/models/wedding.rb index 32c5578..fa6c6e4 100644 --- a/app/models/wedding.rb +++ b/app/models/wedding.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + # == Schema Information # # Table name: weddings diff --git a/db/migrate/20241130182228_create_weddings.rb b/db/migrate/20241130182228_create_weddings.rb index c8cf1fd..2151f72 100644 --- a/db/migrate/20241130182228_create_weddings.rb +++ b/db/migrate/20241130182228_create_weddings.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + class CreateWeddings < ActiveRecord::Migration[8.0] def change create_table :weddings, id: :uuid do |t| diff --git a/db/schema.rb b/db/schema.rb index 41896ca..436f67b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,3 +1,5 @@ +# 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/spec/factories/weddings.rb b/spec/factories/weddings.rb index 5528d3b..fe8e605 100644 --- a/spec/factories/weddings.rb +++ b/spec/factories/weddings.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + FactoryBot.define do factory :wedding do date { 1.year.from_now } diff --git a/spec/models/wedding_spec.rb b/spec/models/wedding_spec.rb index 8725ba8..702769f 100644 --- a/spec/models/wedding_spec.rb +++ b/spec/models/wedding_spec.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + require 'rails_helper' RSpec.describe Wedding, type: :model do From cf6ca5aa17fb6daecb7cfc434fb9debaba816f94 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 19:19:22 +0100 Subject: [PATCH 03/13] Create a seed user for the develoment environment --- db/seeds.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index e7e414d..d1dafe3 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -71,4 +71,11 @@ ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new }) 'red'.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } -Group.roots.each(&:colorize_children) \ No newline at end of file +Group.roots.each(&:colorize_children) + +User.create!( + email: 'development@example.com', + confirmed_at: Time.zone.now, + password: 'supersecretpassword', + password_confirmation: 'supersecretpassword', +) \ No newline at end of file From 988e158d99b330de28f76f8046ce5be4e6d897a9 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 19:57:08 +0100 Subject: [PATCH 04/13] Install acts_as_tenant gem and update documentation --- Gemfile | 1 + Gemfile.lock | 3 +++ config/routes.rb | 32 +++++++++++++---------- db/seeds.rb | 2 +- spec/requests/expenses_spec.rb | 7 +++-- spec/requests/groups_spec.rb | 3 ++- spec/requests/guests_spec.rb | 8 ++++-- spec/requests/schemas.rb | 8 ++++++ spec/requests/users/confirmations_spec.rb | 3 ++- spec/requests/users/registrations_spec.rb | 5 ++-- spec/requests/users/sessions_spec.rb | 7 ++--- 11 files changed, 53 insertions(+), 26 deletions(-) diff --git a/Gemfile b/Gemfile index cc2ec6e..ead69aa 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ gem 'jsonapi-rails' gem 'rack-cors' gem 'react-rails' gem 'rubytree' +gem 'acts_as_tenant' group :development, :test do gem 'annotaterb' diff --git a/Gemfile.lock b/Gemfile.lock index bfe49bd..7e795fc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,8 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) + acts_as_tenant (1.0.1) + rails (>= 6.0) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) annotaterb (4.13.0) @@ -387,6 +389,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + acts_as_tenant annotaterb bootsnap chroma diff --git a/config/routes.rb b/config/routes.rb index 35fd068..b407e01 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,26 +2,30 @@ Rails.application.routes.draw do mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? - devise_for :users, skip: [:registration, :session, :confirmation] - devise_scope :user do - post 'users', to: 'users/registrations#create' + scope ":slug", constraints: {slug: /[a-z]+/} do + devise_for :users, skip: [:registration, :session, :confirmation] + devise_scope :user do + post 'users', to: 'users/registrations#create' - post '/users/sign_in', to: 'users/sessions#create' - delete '/users/sign_out', to: 'users/sessions#destroy' + post '/users/sign_in', to: 'users/sessions#create' + delete '/users/sign_out', to: 'users/sessions#destroy' - get '/users/confirmation', to: 'users/confirmations#show', as: :confirmation + get '/users/confirmation', to: 'users/confirmations#show', as: :confirmation + end + + resources :groups, only: :index + resources :guests, only: %i[index create update destroy] do + post :bulk_update, on: :collection + end + resources :expenses, only: %i[index update] do + get :summary, on: :collection + end + resources :tables_arrangements, only: %i[index show] end + mount Rswag::Ui::Engine => '/api-docs' mount Rswag::Api::Engine => '/api-docs' - resources :groups, only: :index - resources :guests, only: %i[index create update destroy] do - post :bulk_update, on: :collection - end - resources :expenses, only: %i[index update] do - get :summary, on: :collection - end - resources :tables_arrangements, only: %i[index show] get 'up' => 'rails/health#show', as: :rails_health_check diff --git a/db/seeds.rb b/db/seeds.rb index d1dafe3..8b1f91a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -9,7 +9,7 @@ Group.delete_all Wedding.delete_all -Wedding.create!(date: 1.year.from_now) +Wedding.create!(slug: :default, date: 1.year.from_now) Expense.create!(name: 'Photographer', amount: 3000, pricing_type: 'fixed') Expense.create!(name: 'Country house', amount: 6000, pricing_type: 'fixed') diff --git a/spec/requests/expenses_spec.rb b/spec/requests/expenses_spec.rb index 5e99097..74cf861 100644 --- a/spec/requests/expenses_spec.rb +++ b/spec/requests/expenses_spec.rb @@ -3,10 +3,12 @@ require 'swagger_helper' RSpec.describe 'expenses', type: :request do - path '/expenses' do + path '/{slug}/expenses' do get('list expenses') do tags 'Expenses' produces 'application/json' + parameter Swagger::Schema::SLUG + response(200, 'successful') do schema type: :array, items: { @@ -26,12 +28,13 @@ RSpec.describe 'expenses', type: :request do end end - path '/expenses/{id}' do + path '/{slug}/expenses/{id}' do patch('update expense') do tags 'Expenses' consumes 'application/json' produces 'application/json' + parameter Swagger::Schema::SLUG parameter name: 'id', in: :path, type: :string, format: :uuid, description: 'id' parameter name: :body, in: :body, schema: { type: :object, diff --git a/spec/requests/groups_spec.rb b/spec/requests/groups_spec.rb index fd014b5..6d5f057 100644 --- a/spec/requests/groups_spec.rb +++ b/spec/requests/groups_spec.rb @@ -3,10 +3,11 @@ require 'swagger_helper' RSpec.describe 'groups', type: :request do - path '/groups' do + path '/{slug}/groups' do get('list groups') do tags 'Groups' produces 'application/json' + parameter Swagger::Schema::SLUG response(200, 'successful') do schema type: :array, items: { diff --git a/spec/requests/guests_spec.rb b/spec/requests/guests_spec.rb index b73fbcb..9b542fd 100644 --- a/spec/requests/guests_spec.rb +++ b/spec/requests/guests_spec.rb @@ -3,10 +3,11 @@ require 'swagger_helper' RSpec.describe 'guests', type: :request do - path '/guests' do + path '/{slug}/guests' do get('list guests') do tags 'Guests' produces 'application/json' + parameter Swagger::Schema::SLUG response(200, 'successful') do schema type: :array, items: { @@ -33,6 +34,7 @@ RSpec.describe 'guests', type: :request do tags 'Guests' consumes 'application/json' produces 'application/json' + parameter Swagger::Schema::SLUG parameter name: :body, in: :body, schema: { type: :object, required: %i[guest], @@ -55,11 +57,12 @@ RSpec.describe 'guests', type: :request do end end - path '/guests/{id}' do + path '/{slug}/guests/{id}' do patch('update guest') do tags 'Guests' consumes 'application/json' produces 'application/json' + parameter Swagger::Schema::SLUG parameter name: 'id', in: :path, type: :string, format: :uuid parameter name: :body, in: :body, schema: { type: :object, @@ -85,6 +88,7 @@ RSpec.describe 'guests', type: :request do delete('delete guest') do tags 'Guests' produces 'application/json' + parameter Swagger::Schema::SLUG parameter name: 'id', in: :path, type: :string, format: :uuid response_empty_200 diff --git a/spec/requests/schemas.rb b/spec/requests/schemas.rb index 7a49ca0..ce35ad7 100644 --- a/spec/requests/schemas.rb +++ b/spec/requests/schemas.rb @@ -9,5 +9,13 @@ module Swagger updated_at: SwaggerResponseHelper::TIMESTAMP } + + SLUG = { + name: 'slug', + in: :path, + type: :string, + example: :default, + description: 'Wedding slug' + } end end \ No newline at end of file diff --git a/spec/requests/users/confirmations_spec.rb b/spec/requests/users/confirmations_spec.rb index e99794e..12bdfbc 100644 --- a/spec/requests/users/confirmations_spec.rb +++ b/spec/requests/users/confirmations_spec.rb @@ -4,11 +4,12 @@ require 'swagger_helper' RSpec.describe 'users/confirmations', type: :request do - path '/users/confirmation' do + path '/{slug}/users/confirmation' do get('confirm user email') do tags 'Users' produces 'application/json' + parameter Swagger::Schema::SLUG parameter name: :confirmation_token, in: :query, type: :string, required: true response(200, 'confirmed') do diff --git a/spec/requests/users/registrations_spec.rb b/spec/requests/users/registrations_spec.rb index f92222c..aa7562f 100644 --- a/spec/requests/users/registrations_spec.rb +++ b/spec/requests/users/registrations_spec.rb @@ -4,12 +4,13 @@ require 'swagger_helper' RSpec.describe 'users/registrations', type: :request do - path '/users' do + path '/{slug}/users' do post('create registration') do tags 'Users Registrations' consumes 'application/json' produces 'application/json' - + + parameter Swagger::Schema::SLUG parameter name: :body, in: :body, schema: { type: :object, required: [:user], diff --git a/spec/requests/users/sessions_spec.rb b/spec/requests/users/sessions_spec.rb index ff7c8a3..a803965 100644 --- a/spec/requests/users/sessions_spec.rb +++ b/spec/requests/users/sessions_spec.rb @@ -4,13 +4,14 @@ require 'swagger_helper' RSpec.describe 'users/sessions', type: :request do - path '/users/sign_in' do + path '/{slug}/users/sign_in' do post('create session') do tags 'Users Sessions' consumes 'application/json' produces 'application/json' + parameter Swagger::Schema::SLUG parameter name: :body, in: :body, schema: { type: :object, required: %i[user], @@ -35,8 +36,8 @@ RSpec.describe 'users/sessions', type: :request do end end - path '/users/sign_out' do - + path '/{slug}/users/sign_out' do + parameter Swagger::Schema::SLUG delete('delete session') do tags 'Users Sessions' consumes 'application/json' From 8bff98b165294e6003ad4ff2e93e81242e4a73c2 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 20:04:17 +0100 Subject: [PATCH 05/13] Create DB associations --- app/models/expense.rb | 10 ++++++++++ app/models/group.rb | 9 +++++++-- app/models/guest.rb | 6 +++++- app/models/seat.rb | 4 ++++ app/models/tables_arrangement.rb | 10 ++++++++++ .../20241130185731_add_wedding_id_to_models.rb | 7 +++++++ db/schema.rb | 17 ++++++++++++++++- 7 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20241130185731_add_wedding_id_to_models.rb diff --git a/app/models/expense.rb b/app/models/expense.rb index 6da98af..5601c30 100644 --- a/app/models/expense.rb +++ b/app/models/expense.rb @@ -10,8 +10,18 @@ # pricing_type :enum default("fixed"), not null # created_at :datetime not null # updated_at :datetime not null +# wedding_id :uuid not null +# +# Indexes +# +# index_expenses_on_wedding_id (wedding_id) +# +# Foreign Keys +# +# fk_rails_... (wedding_id => weddings.id) # class Expense < ApplicationRecord + acts_as_tenant :wedding enum :pricing_type, fixed: 'fixed', per_person: 'per_person' diff --git a/app/models/group.rb b/app/models/group.rb index fe11e0b..5056b28 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -12,17 +12,22 @@ # created_at :datetime not null # updated_at :datetime not null # parent_id :uuid +# wedding_id :uuid not null # # Indexes # -# index_groups_on_name (name) UNIQUE -# index_groups_on_parent_id (parent_id) +# index_groups_on_name (name) UNIQUE +# index_groups_on_parent_id (parent_id) +# index_groups_on_wedding_id (wedding_id) # # Foreign Keys # # fk_rails_... (parent_id => groups.id) +# fk_rails_... (wedding_id => weddings.id) # class Group < ApplicationRecord + acts_as_tenant :wedding + validates :name, uniqueness: true validates :name, :order, presence: true diff --git a/app/models/guest.rb b/app/models/guest.rb index 40c41be..8983c66 100644 --- a/app/models/guest.rb +++ b/app/models/guest.rb @@ -11,16 +11,20 @@ # created_at :datetime not null # updated_at :datetime not null # group_id :uuid not null +# wedding_id :uuid not null # # Indexes # -# index_guests_on_group_id (group_id) +# index_guests_on_group_id (group_id) +# index_guests_on_wedding_id (wedding_id) # # Foreign Keys # # fk_rails_... (group_id => groups.id) +# fk_rails_... (wedding_id => weddings.id) # class Guest < ApplicationRecord + acts_as_tenant :wedding belongs_to :group enum :status, { diff --git a/app/models/seat.rb b/app/models/seat.rb index 1f3769c..3f13629 100644 --- a/app/models/seat.rb +++ b/app/models/seat.rb @@ -10,18 +10,22 @@ # updated_at :datetime not null # guest_id :uuid not null # tables_arrangement_id :uuid not null +# wedding_id :uuid not null # # Indexes # # index_seats_on_guest_id (guest_id) # index_seats_on_tables_arrangement_id (tables_arrangement_id) +# index_seats_on_wedding_id (wedding_id) # # Foreign Keys # # fk_rails_... (guest_id => guests.id) # fk_rails_... (tables_arrangement_id => tables_arrangements.id) ON DELETE => cascade +# fk_rails_... (wedding_id => weddings.id) # class Seat < ApplicationRecord + acts_as_tenant :wedding belongs_to :guest belongs_to :table_arrangement end diff --git a/app/models/tables_arrangement.rb b/app/models/tables_arrangement.rb index 6e2f621..3080e49 100644 --- a/app/models/tables_arrangement.rb +++ b/app/models/tables_arrangement.rb @@ -9,8 +9,18 @@ # name :string not null # created_at :datetime not null # updated_at :datetime not null +# wedding_id :uuid not null +# +# Indexes +# +# index_tables_arrangements_on_wedding_id (wedding_id) +# +# Foreign Keys +# +# fk_rails_... (wedding_id => weddings.id) # class TablesArrangement < ApplicationRecord + acts_as_tenant :wedding has_many :seats has_many :guests, through: :seats diff --git a/db/migrate/20241130185731_add_wedding_id_to_models.rb b/db/migrate/20241130185731_add_wedding_id_to_models.rb new file mode 100644 index 0000000..bf505b5 --- /dev/null +++ b/db/migrate/20241130185731_add_wedding_id_to_models.rb @@ -0,0 +1,7 @@ +class AddWeddingIdToModels < ActiveRecord::Migration[8.0] + def change + [:expenses, :guests, :seats, :tables_arrangements, :groups].each do |table| + add_reference table, :wedding, type: :uuid, null: false, foreign_key: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 436f67b..fd07a1b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do +ActiveRecord::Schema[8.0].define(version: 2024_11_30_185731) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -26,6 +26,8 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do t.enum "pricing_type", default: "fixed", null: false, enum_type: "pricing_types" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.uuid "wedding_id", null: false + t.index ["wedding_id"], name: "index_expenses_on_wedding_id" end create_table "groups", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -36,8 +38,10 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do t.datetime "updated_at", null: false t.uuid "parent_id" t.string "color" + t.uuid "wedding_id", null: false t.index ["name"], name: "index_groups_on_name", unique: true t.index ["parent_id"], name: "index_groups_on_parent_id" + t.index ["wedding_id"], name: "index_groups_on_wedding_id" end create_table "guests", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -47,7 +51,9 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do t.uuid "group_id", null: false t.integer "status", default: 0 t.string "name" + t.uuid "wedding_id", null: false t.index ["group_id"], name: "index_guests_on_group_id" + t.index ["wedding_id"], name: "index_guests_on_wedding_id" end create_table "seats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -56,8 +62,10 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do t.integer "table_number" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.uuid "wedding_id", null: false t.index ["guest_id"], name: "index_seats_on_guest_id" t.index ["tables_arrangement_id"], name: "index_seats_on_tables_arrangement_id" + t.index ["wedding_id"], name: "index_seats_on_wedding_id" end create_table "solid_queue_blocked_executions", force: :cascade do |t| @@ -186,6 +194,8 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "name", null: false + t.uuid "wedding_id", null: false + t.index ["wedding_id"], name: "index_tables_arrangements_on_wedding_id" end create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -216,14 +226,19 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_182228) do t.index ["slug"], name: "index_weddings_on_slug", unique: true end + add_foreign_key "expenses", "weddings" add_foreign_key "groups", "groups", column: "parent_id" + add_foreign_key "groups", "weddings" add_foreign_key "guests", "groups" + add_foreign_key "guests", "weddings" add_foreign_key "seats", "guests" add_foreign_key "seats", "tables_arrangements", on_delete: :cascade + add_foreign_key "seats", "weddings" 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" end From 5b3c1fdfac6efce10e5ecebfbb516bac8d372d1a Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 20:06:36 +0100 Subject: [PATCH 06/13] Adapt seeds file to use ActsAsTenant --- db/seeds.rb | 113 ++++++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 8b1f91a..5e9886a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -9,70 +9,69 @@ Group.delete_all Wedding.delete_all -Wedding.create!(slug: :default, date: 1.year.from_now) +ActsAsTenant.with_tenant(Wedding.create!(slug: :default, date: 1.year.from_now)) 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') -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') - -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| - family.children.create!(name: "Jim's close family", icon: 'pi pi-home') - family.children.create!(name: "Jim's cousins", icon: 'pi pi-home') - family.children.create!(name: "Jim's relatives", icon: 'pi pi-home') + 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| + family.children.create!(name: "Jim's close family", icon: 'pi pi-home') + family.children.create!(name: "Jim's cousins", icon: 'pi pi-home') + family.children.create!(name: "Jim's relatives", icon: 'pi pi-home') + end + parent.children.create!(name: "Jim's friends", icon: 'pi pi-bullseye') + parent.children.create!(name: "Jim's work", icon: 'pi pi-desktop').tap do |work| + work.children.create!(name: "Jim's besties at work", icon: 'pi pi-briefcase') + end end - parent.children.create!(name: "Jim's friends", icon: 'pi pi-bullseye') - parent.children.create!(name: "Jim's work", icon: 'pi pi-desktop').tap do |work| - work.children.create!(name: "Jim's besties at work", icon: 'pi pi-briefcase') + + Group.create!(name: "Pam's guests", icon: 'pi pi-heart-fill').tap do |parent| + parent.children.create!(name: "Pam's family", icon: 'pi pi-users').tap do |family| + family.children.create!(name: "Pam's close family", icon: 'pi pi-home') + family.children.create!(name: "Pam's cousins", icon: 'pi pi-home') + family.children.create!(name: "Pam's relatives", icon: 'pi pi-home') + end + parent.children.create!(name: "Pam's friends", icon: 'pi pi-bullseye') + parent.children.create!(name: "Pam's work", icon: 'pi pi-desktop').tap do |work| + work.children.create!(name: "Pam's besties at work", icon: 'pi pi-briefcase') + end end -end -Group.create!(name: "Pam's guests", icon: 'pi pi-heart-fill').tap do |parent| - parent.children.create!(name: "Pam's family", icon: 'pi pi-users').tap do |family| - family.children.create!(name: "Pam's close family", icon: 'pi pi-home') - family.children.create!(name: "Pam's cousins", icon: 'pi pi-home') - family.children.create!(name: "Pam's relatives", icon: 'pi pi-home') + Group.create!(name: 'Common guests', icon: 'pi pi-users').tap do |parent| + parent.children.create!(name: 'College friends', icon: 'pi pi-calculator') + parent.children.create!(name: 'High school friends', icon: 'pi pi-crown') + parent.children.create!(name: 'Childhood friends', icon: 'pi pi-envelope') end - parent.children.create!(name: "Pam's friends", icon: 'pi pi-bullseye') - parent.children.create!(name: "Pam's work", icon: 'pi pi-desktop').tap do |work| - work.children.create!(name: "Pam's besties at work", icon: 'pi pi-briefcase') + + groups = Group.all + + NUMBER_OF_GUESTS.times do + Guest.create!( + name: Faker::Name.name, + phone: Faker::PhoneNumber.cell_phone, + group: groups.sample, + status: Guest.statuses.keys.sample + ) end + + ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new }) + + 'red'.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } + + Group.roots.each(&:colorize_children) end - -Group.create!(name: 'Common guests', icon: 'pi pi-users').tap do |parent| - parent.children.create!(name: 'College friends', icon: 'pi pi-calculator') - parent.children.create!(name: 'High school friends', icon: 'pi pi-crown') - parent.children.create!(name: 'Childhood friends', icon: 'pi pi-envelope') -end - -groups = Group.all - -NUMBER_OF_GUESTS.times do - Guest.create!( - name: Faker::Name.name, - phone: Faker::PhoneNumber.cell_phone, - group: groups.sample, - status: Guest.statuses.keys.sample - ) -end - -ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new }) - -'red'.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } - -Group.roots.each(&:colorize_children) - User.create!( email: 'development@example.com', confirmed_at: Time.zone.now, From cb90a93ef343ac11faf3e60472e89e20ce93b72a Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 19:07:36 +0000 Subject: [PATCH 07/13] Add copyright notice --- db/migrate/20241130185731_add_wedding_id_to_models.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/migrate/20241130185731_add_wedding_id_to_models.rb b/db/migrate/20241130185731_add_wedding_id_to_models.rb index bf505b5..c57fff4 100644 --- a/db/migrate/20241130185731_add_wedding_id_to_models.rb +++ b/db/migrate/20241130185731_add_wedding_id_to_models.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + class AddWeddingIdToModels < ActiveRecord::Migration[8.0] def change [:expenses, :guests, :seats, :tables_arrangements, :groups].each do |table| From 63bb32f2a7abbbc98d0a42a2365460f1a93ab3f0 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 21:06:21 +0100 Subject: [PATCH 08/13] Include users in the list of models affected by tenant --- app/models/user.rb | 8 ++++++++ .../20241130185731_add_wedding_id_to_models.rb | 2 +- db/schema.rb | 5 +++-- db/seeds.rb | 13 +++++++------ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index d338416..43c3a9e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,6 +18,7 @@ # unlock_token :string # created_at :datetime not null # updated_at :datetime not null +# wedding_id :uuid not null # # Indexes # @@ -25,8 +26,15 @@ # index_users_on_email (email) UNIQUE # index_users_on_reset_password_token (reset_password_token) UNIQUE # index_users_on_unlock_token (unlock_token) UNIQUE +# index_users_on_wedding_id (wedding_id) +# +# Foreign Keys +# +# fk_rails_... (wedding_id => weddings.id) # class User < ApplicationRecord + acts_as_tenant :wedding + devise :database_authenticatable, :registerable, :recoverable, :validatable, :confirmable, :lockable end diff --git a/db/migrate/20241130185731_add_wedding_id_to_models.rb b/db/migrate/20241130185731_add_wedding_id_to_models.rb index c57fff4..63e5c32 100644 --- a/db/migrate/20241130185731_add_wedding_id_to_models.rb +++ b/db/migrate/20241130185731_add_wedding_id_to_models.rb @@ -2,7 +2,7 @@ class AddWeddingIdToModels < ActiveRecord::Migration[8.0] def change - [:expenses, :guests, :seats, :tables_arrangements, :groups].each do |table| + [:expenses, :guests, :seats, :tables_arrangements, :groups, :users].each do |table| add_reference table, :wedding, type: :uuid, null: false, foreign_key: true end end diff --git a/db/schema.rb b/db/schema.rb index fd07a1b..d70dbfa 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. @@ -212,10 +210,12 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_185731) do t.datetime "locked_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.uuid "wedding_id", null: false t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true + t.index ["wedding_id"], name: "index_users_on_wedding_id" end create_table "weddings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -241,4 +241,5 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_30_185731) do 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" end diff --git a/db/seeds.rb b/db/seeds.rb index 5e9886a..206f7da 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -71,10 +71,11 @@ ActsAsTenant.with_tenant(Wedding.create!(slug: :default, date: 1.year.from_now)) 'red'.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) } Group.roots.each(&:colorize_children) + + User.create!( + email: 'development@example.com', + confirmed_at: Time.zone.now, + password: 'supersecretpassword', + password_confirmation: 'supersecretpassword', + ) end -User.create!( - email: 'development@example.com', - confirmed_at: Time.zone.now, - password: 'supersecretpassword', - password_confirmation: 'supersecretpassword', -) \ No newline at end of file From 6d61e8452a3087555bf2844debf7c9a0f333d0bc Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 20:07:45 +0000 Subject: [PATCH 09/13] Add copyright notice --- db/schema.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/schema.rb b/db/schema.rb index d70dbfa..7ff33f3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,3 +1,5 @@ +# 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. From be3497ad646ac9a86ff37d71b6e69b27a884369e Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 30 Nov 2024 21:11:25 +0100 Subject: [PATCH 10/13] Configure current tenant in a before_action of the ApplicationController --- app/controllers/application_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0d2a0c7..13400e3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,7 @@ # Copyright (C) 2024 Manuel Bustillo class ApplicationController < ActionController::Base + before_action :set_tenant before_action :authenticate_user! after_action :set_csrf_cookie @@ -29,6 +30,10 @@ class ApplicationController < ActionController::Base private + def set_tenant + ActsAsTenant.current_tenant = Wedding.find_by(slug: params[:slug]) + end + def development_swagger? Rails.env.test? || Rails.env.development? && request.headers['referer']&.include?('/api-docs/index.html') From 8429b3952be8c78331cce99ca3b5cf163892adf6 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sun, 1 Dec 2024 09:58:39 +0100 Subject: [PATCH 11/13] Adapt factories to use a wedding object --- spec/factories/expense.rb | 1 + spec/factories/groups.rb | 1 + spec/factories/guest.rb | 3 ++- spec/factories/table_arrangement.rb | 6 ++++++ spec/factories/users.rb | 2 +- spec/factories/weddings.rb | 1 + spec/models/tables_arrangement_spec.rb | 2 +- 7 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 spec/factories/table_arrangement.rb diff --git a/spec/factories/expense.rb b/spec/factories/expense.rb index 9670768..726d94d 100644 --- a/spec/factories/expense.rb +++ b/spec/factories/expense.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :expense do + wedding sequence(:name) { |i| "Expense #{i}" } pricing_type { "fixed" } amount { 100 } diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 4ebce4e..ccd1156 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :group do + wedding sequence(:name) { |i| "Group #{i}" } order { 1 } end diff --git a/spec/factories/guest.rb b/spec/factories/guest.rb index 4c9b58a..28f3b80 100644 --- a/spec/factories/guest.rb +++ b/spec/factories/guest.rb @@ -2,7 +2,8 @@ FactoryBot.define do factory :guest do - association :group + group + wedding name { Faker::Name.name } phone { Faker::PhoneNumber.cell_phone } diff --git a/spec/factories/table_arrangement.rb b/spec/factories/table_arrangement.rb new file mode 100644 index 0000000..5e914ce --- /dev/null +++ b/spec/factories/table_arrangement.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :tables_arrangement do + wedding + end +end + \ No newline at end of file diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 7839fbe..2aa8a37 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -2,6 +2,6 @@ FactoryBot.define do factory :user do - + wedding end end diff --git a/spec/factories/weddings.rb b/spec/factories/weddings.rb index fe8e605..3680045 100644 --- a/spec/factories/weddings.rb +++ b/spec/factories/weddings.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :wedding do + sequence(:slug) { |i| "wedding-#{i}" } date { 1.year.from_now } end end diff --git a/spec/models/tables_arrangement_spec.rb b/spec/models/tables_arrangement_spec.rb index c15971a..4db0859 100644 --- a/spec/models/tables_arrangement_spec.rb +++ b/spec/models/tables_arrangement_spec.rb @@ -5,7 +5,7 @@ require 'rails_helper' RSpec.describe TablesArrangement, type: :model do describe 'callbacks' do it 'assigns a name before creation' do - expect(described_class.create!.name).to be_present + expect(create(:tables_arrangement).name).to be_present end end end From 70b44e1b96ed49f2e4944a73c60c2ee02073a938 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sun, 1 Dec 2024 09:00:01 +0000 Subject: [PATCH 12/13] Add copyright notice --- spec/factories/table_arrangement.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/factories/table_arrangement.rb b/spec/factories/table_arrangement.rb index 5e914ce..693e68a 100644 --- a/spec/factories/table_arrangement.rb +++ b/spec/factories/table_arrangement.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + FactoryBot.define do factory :tables_arrangement do wedding From 4d9563cab735154a2fbc417666959e9efd4a974a Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sun, 1 Dec 2024 10:41:05 +0100 Subject: [PATCH 13/13] Adapt background job to use acts as tenant --- app/jobs/table_simulator_job.rb | 22 ++++++++++++---------- app/models/guest.rb | 2 +- db/seeds.rb | 5 +++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/jobs/table_simulator_job.rb b/app/jobs/table_simulator_job.rb index ef91719..ced9b2d 100644 --- a/app/jobs/table_simulator_job.rb +++ b/app/jobs/table_simulator_job.rb @@ -3,21 +3,23 @@ class TableSimulatorJob < ApplicationJob queue_as :default - def perform(*_args) - engine = VNS::Engine.new + def perform(wedding_id) + ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do + engine = VNS::Engine.new - engine.add_perturbation(Tables::Swap) - engine.add_perturbation(Tables::Shift) + engine.add_perturbation(Tables::Swap) + engine.add_perturbation(Tables::Shift) - initial_solution = Tables::Distribution.new(min_per_table: 8, max_per_table: 10) - initial_solution.random_distribution(Guest.potential.shuffle) + initial_solution = Tables::Distribution.new(min_per_table: 8, max_per_table: 10) + initial_solution.random_distribution(Guest.potential.shuffle) - engine.initial_solution = initial_solution + engine.initial_solution = initial_solution - engine.target_function(&:discomfort) + engine.target_function(&:discomfort) - best_solution = engine.run + best_solution = engine.run - best_solution.save! + best_solution.save! + end end end diff --git a/app/models/guest.rb b/app/models/guest.rb index 8983c66..929cc6e 100644 --- a/app/models/guest.rb +++ b/app/models/guest.rb @@ -49,6 +49,6 @@ class Guest < ApplicationRecord def recalculate_simulations TablesArrangement.delete_all - ActiveJob.perform_all_later(50.times.map { TableSimulatorJob.new }) + ActiveJob.perform_all_later(50.times.map { TableSimulatorJob.new(wedding_id) }) end end diff --git a/db/seeds.rb b/db/seeds.rb index 206f7da..e493f6b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -8,8 +8,9 @@ Guest.delete_all Group.delete_all Wedding.delete_all +wedding = Wedding.create!(slug: :default, date: 1.year.from_now) -ActsAsTenant.with_tenant(Wedding.create!(slug: :default, date: 1.year.from_now)) do +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') @@ -66,7 +67,7 @@ ActsAsTenant.with_tenant(Wedding.create!(slug: :default, date: 1.year.from_now)) ) end - ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new }) + 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)) }