diff --git a/app/models/shopkeeper.rb b/app/models/shopkeeper.rb index f221df4..f7e53ac 100644 --- a/app/models/shopkeeper.rb +++ b/app/models/shopkeeper.rb @@ -10,8 +10,8 @@ class Shopkeeper < ApplicationRecord has_many :shops, through: :accounts has_many :item_tags, through: :shops has_many :created_shops, class_name: "Shop", foreign_key: :created_by_id, inverse_of: :created_by - has_many :created_item_tags, class_name: "ItemTag", foreign_key: :created_by_id, inverse_of: :created_by - has_many :completed_item_tags, class_name: "ItemTag", foreign_key: :completed_by_id, inverse_of: :completed_by + has_many :created_item_tags, class_name: "ItemTag", foreign_key: :created_by_id, inverse_of: :created_by, dependent: :nullify + has_many :completed_item_tags, class_name: "ItemTag", foreign_key: :completed_by_id, inverse_of: :completed_by, dependent: :nullify attribute :token, :string attribute :client, :string diff --git a/db/migrate/20260409072237_change_shopkeeper_foreign_keys_to_nullify_on_delete.rb b/db/migrate/20260409072237_change_shopkeeper_foreign_keys_to_nullify_on_delete.rb new file mode 100644 index 0000000..f25b737 --- /dev/null +++ b/db/migrate/20260409072237_change_shopkeeper_foreign_keys_to_nullify_on_delete.rb @@ -0,0 +1,17 @@ +class ChangeShopkeeperForeignKeysToNullifyOnDelete < ActiveRecord::Migration[8.1] + def up + remove_foreign_key :item_tags, column: :completed_by_id + remove_foreign_key :item_tags, column: :created_by_id + + add_foreign_key :item_tags, :shopkeepers, column: :completed_by_id, on_delete: :nullify + add_foreign_key :item_tags, :shopkeepers, column: :created_by_id, on_delete: :nullify + end + + def down + remove_foreign_key :item_tags, column: :completed_by_id + remove_foreign_key :item_tags, column: :created_by_id + + add_foreign_key :item_tags, :shopkeepers, column: :completed_by_id + add_foreign_key :item_tags, :shopkeepers, column: :created_by_id + end +end diff --git a/db/schema.rb b/db/schema.rb index e518962..67405e5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,28 +10,28 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_03_06_071848) do +ActiveRecord::Schema[8.1].define(version: 2026_04_09_072237) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_catalog.plpgsql" enable_extension "pgcrypto" - enable_extension "plpgsql" create_table "accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false t.string "name", null: false t.uuid "owner_id", null: false t.boolean "personal", default: false, null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["owner_id"], name: "index_accounts_on_owner_id" end create_table "accounts_invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "account_id", null: false + t.datetime "created_at", null: false + t.string "email", null: false t.uuid "invited_by_id" - t.string "token", null: false t.string "name", null: false - t.string "email", null: false t.jsonb "roles", default: {}, null: false - t.datetime "created_at", null: false + t.string "token", null: false t.datetime "updated_at", null: false t.index ["account_id"], name: "index_accounts_invitations_on_account_id" t.index ["invited_by_id"], name: "index_accounts_invitations_on_invited_by_id" @@ -40,33 +40,33 @@ create_table "accounts_shopkeepers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "account_id", null: false - t.uuid "shopkeeper_id", null: false - t.jsonb "roles", default: {}, null: false t.datetime "created_at", null: false + t.jsonb "roles", default: {}, null: false + t.uuid "shopkeeper_id", null: false t.datetime "updated_at", null: false t.index ["account_id"], name: "index_accounts_shopkeepers_on_account_id" t.index ["shopkeeper_id"], name: "index_accounts_shopkeepers_on_shopkeeper_id" end create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.uuid "record_id", null: false t.uuid "blob_id", null: false t.datetime "created_at", null: false + t.string "name", null: false + t.uuid "record_id", null: false + t.string "record_type", null: false t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end create_table "active_storage_blobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.string "service_name", null: false t.bigint "byte_size", null: false t.string "checksum" + t.string "content_type" t.datetime "created_at", null: false + t.string "filename", null: false + t.string "key", null: false + t.text "metadata" + t.string "service_name", null: false t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end @@ -77,39 +77,39 @@ end create_table "admin_users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "name", null: false + t.datetime "created_at", null: false t.string "email", null: false + t.string "name", null: false t.string "password_digest", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_admin_users_on_email", unique: true end create_table "app_versions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "platform", null: false - t.integer "version", null: false + t.datetime "created_at", null: false t.integer "current_type", default: 1, null: false + t.text "description", null: false t.integer "forced_update_type", default: 1, null: false + t.string "platform", null: false t.string "title", null: false - t.text "description", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "version", null: false t.index ["platform", "version"], name: "index_app_versions_on_platform_and_version", unique: true end create_table "item_tags", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "account_id", null: false - t.uuid "shop_id", null: false - t.uuid "created_by_id" + t.boolean "already_completed", default: false, null: false + t.datetime "completed_at", precision: nil t.uuid "completed_by_id" + t.datetime "created_at", null: false + t.uuid "created_by_id" + t.datetime "customer_read_at", precision: nil t.string "queue_number", null: false + t.integer "scan_state", default: 1, null: false + t.uuid "shop_id", null: false t.integer "state", default: 1, null: false - t.datetime "customer_read_at", precision: nil - t.datetime "completed_at", precision: nil - t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "scan_state", default: 1, null: false - t.boolean "already_completed", default: false, null: false t.index ["account_id"], name: "index_item_tags_on_account_id" t.index ["completed_by_id"], name: "index_item_tags_on_completed_by_id" t.index ["created_by_id"], name: "index_item_tags_on_created_by_id" @@ -120,38 +120,38 @@ end create_table "permissions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false t.string "name", null: false - t.string "tag", null: false t.integer "position" - t.datetime "created_at", null: false + t.string "tag", null: false t.datetime "updated_at", null: false t.index ["tag"], name: "index_permissions_on_tag", unique: true end create_table "privacy_versions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.integer "version", null: false + t.datetime "created_at", null: false t.integer "current_type", default: 1, null: false + t.text "description", null: false t.datetime "published_at", null: false t.string "title", null: false - t.text "description", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "version", null: false t.index ["version"], name: "index_privacy_versions_on_version", unique: true end create_table "roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false t.string "name", null: false - t.string "tag", null: false t.integer "position" - t.datetime "created_at", null: false + t.string "tag", null: false t.datetime "updated_at", null: false t.index ["tag"], name: "index_roles_on_tag", unique: true end create_table "roles_permissions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "role_id", null: false - t.uuid "permission_id", null: false t.datetime "created_at", null: false + t.uuid "permission_id", null: false + t.uuid "role_id", null: false t.datetime "updated_at", null: false t.index ["permission_id"], name: "index_roles_permissions_on_permission_id" t.index ["role_id", "permission_id"], name: "index_roles_permissions_on_role_id_and_permission_id", unique: true @@ -159,34 +159,34 @@ end create_table "shopkeepers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "provider", default: "email", null: false - t.string "uid", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil t.boolean "allow_password_change", default: false - t.datetime "remember_created_at", precision: nil + t.datetime "confirmation_sent_at", precision: nil t.string "confirmation_token" t.datetime "confirmed_at", precision: nil - t.datetime "confirmation_sent_at", precision: nil - t.string "unconfirmed_email" - t.string "name" - t.string "nickname" - t.string "image" - t.string "email" - t.json "tokens" - t.integer "sign_in_count", default: 0, null: false + t.integer "confirmed_privacy_version", default: 1, null: false + t.integer "confirmed_terms_version", default: 1, null: false + t.datetime "created_at", null: false + t.string "current_platform", default: "", null: false t.datetime "current_sign_in_at", precision: nil - t.datetime "last_sign_in_at", precision: nil t.inet "current_sign_in_ip" + t.string "email" + t.string "encrypted_password", default: "", null: false + t.string "image" + t.datetime "last_sign_in_at", precision: nil t.inet "last_sign_in_ip" t.string "locale", default: "en", null: false + t.string "name" + t.string "nickname" + t.string "provider", default: "email", null: false + t.datetime "remember_created_at", precision: nil + t.datetime "reset_password_sent_at", precision: nil + t.string "reset_password_token" + t.integer "sign_in_count", default: 0, null: false t.string "time_zone", default: "London", null: false - t.datetime "created_at", null: false + t.json "tokens" + t.string "uid", default: "", null: false + t.string "unconfirmed_email" t.datetime "updated_at", null: false - t.string "current_platform", default: "", null: false - t.integer "confirmed_privacy_version", default: 1, null: false - t.integer "confirmed_terms_version", default: 1, null: false t.index ["confirmation_token"], name: "index_shopkeepers_on_confirmation_token", unique: true t.index ["email"], name: "index_shopkeepers_on_email", unique: true t.index ["reset_password_token"], name: "index_shopkeepers_on_reset_password_token", unique: true @@ -195,24 +195,24 @@ create_table "shops", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "account_id", null: false + t.datetime "created_at", null: false t.uuid "created_by_id", null: false + t.text "description" t.string "name", null: false t.string "time_zone", default: "London", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "description" t.index ["account_id"], name: "index_shops_on_account_id" t.index ["created_by_id"], name: "index_shops_on_created_by_id" end create_table "terms_versions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.integer "version", null: false + t.datetime "created_at", null: false t.integer "current_type", default: 1, null: false + t.text "description", null: false t.datetime "published_at", null: false t.string "title", null: false - t.text "description", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "version", null: false t.index ["version"], name: "index_terms_versions_on_version", unique: true end @@ -224,8 +224,8 @@ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "item_tags", "accounts" - add_foreign_key "item_tags", "shopkeepers", column: "completed_by_id" - add_foreign_key "item_tags", "shopkeepers", column: "created_by_id" + add_foreign_key "item_tags", "shopkeepers", column: "completed_by_id", on_delete: :nullify + add_foreign_key "item_tags", "shopkeepers", column: "created_by_id", on_delete: :nullify add_foreign_key "item_tags", "shops" add_foreign_key "roles_permissions", "permissions" add_foreign_key "roles_permissions", "roles" diff --git a/test/controllers/shopkeeper_auth/registrations_controller_test.rb b/test/controllers/shopkeeper_auth/registrations_controller_test.rb index 7fd32f3..707918a 100644 --- a/test/controllers/shopkeeper_auth/registrations_controller_test.rb +++ b/test/controllers/shopkeeper_auth/registrations_controller_test.rb @@ -30,6 +30,21 @@ class ShopkeeperAuth::RegistrationsControllerTest < ActionDispatch::IntegrationT end end + test "delete current shopkeeper with item_tags" do + shopkeeper.create_default_account + account = shopkeeper.accounts.first + + ActsAsTenant.with_tenant(account) do + shop = account.shops.create!(name: "Test Shop", created_by: shopkeeper) + shop.item_tags.create!(queue_number: "A1", account: account, completed_by: shopkeeper) + end + + assert_difference "Shopkeeper.count", -1 do + delete shopkeeper_registration_url, headers: shopkeeper.create_new_auth_token + assert_response :success + end + end + def shopkeeper @shopkeeper ||= shopkeepers(:one) end diff --git a/test/models/shopkeeper_test.rb b/test/models/shopkeeper_test.rb index ec19230..78568fb 100644 --- a/test/models/shopkeeper_test.rb +++ b/test/models/shopkeeper_test.rb @@ -213,4 +213,59 @@ class ShopkeeperTest < ActiveSupport::TestCase shopkeeper.destroy end end + + test "should nullify item_tags references on destroy" do + shopkeeper = shopkeepers(:one) + shopkeeper.create_default_account + other_shopkeeper = shopkeepers(:two) + other_shopkeeper.create_default_account + other_account = other_shopkeeper.accounts.first + + item_tag = ActsAsTenant.with_tenant(other_account) do + shop = other_account.shops.create!(name: "Other Shop", created_by: other_shopkeeper) + shop.item_tags.create!( + queue_number: "A1", + account: other_account, + completed_by: shopkeeper + ) + end + + ActsAsTenant.without_tenant do + shopkeeper.destroy + end + + ActsAsTenant.with_tenant(other_account) do + item_tag.reload + assert_nil item_tag.completed_by_id + assert ItemTag.exists?(item_tag.id) + end + end + + test "should successfully destroy shopkeeper with item_tags in other accounts" do + shopkeeper = shopkeepers(:one) + shopkeeper.create_default_account + other_shopkeeper = shopkeepers(:two) + other_shopkeeper.create_default_account + other_account = other_shopkeeper.accounts.first + + item_tag = ActsAsTenant.with_tenant(other_account) do + shop = other_account.shops.create!(name: "Other Shop", created_by: other_shopkeeper) + shop.item_tags.create!( + queue_number: "B1", + account: other_account, + created_by: shopkeeper + ) + end + + ActsAsTenant.without_tenant do + assert_nothing_raised do + shopkeeper.destroy! + end + end + + ActsAsTenant.with_tenant(other_account) do + item_tag.reload + assert_nil item_tag.created_by_id + end + end end