From 78f0b2ecf4dfc86de1a7c92797482e31257d213c Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Fri, 31 Jul 2026 12:46:19 +0200 Subject: [PATCH] style(rubocop): resolve Rails/UniqueValidationWithoutIndex with DB indexes --- .rubocop_todo.yml | 11 +---------- app/models/auth_service.rb | 2 +- ...6_add_unique_indexes_for_unique_validations.rb | 15 +++++++++++++++ db/schema.rb | 11 ++++++++++- 4 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 db/migrate/20260731104506_add_unique_indexes_for_unique_validations.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0644bf7a7..f9667bcc9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -135,13 +135,4 @@ Rails/SkipsModelValidations: - 'app/controllers/workshop_invitation_controller.rb' - 'app/services/invitation_logger.rb' -# Offense count: 8 -Rails/UniqueValidationWithoutIndex: - Exclude: - - 'app/models/auth_service.rb' - - 'app/models/chapter.rb' - - 'app/models/feedback_request.rb' - - 'app/models/invitation.rb' - - 'app/models/meeting_invitation.rb' - - 'app/models/workshop_invitation.rb' - - 'app/models/workshop_sponsor.rb' + diff --git a/app/models/auth_service.rb b/app/models/auth_service.rb index 4618b0617..4bc192056 100644 --- a/app/models/auth_service.rb +++ b/app/models/auth_service.rb @@ -1,4 +1,4 @@ class AuthService < ApplicationRecord belongs_to :member, optional: true - validates :uid, uniqueness: { constraint: :provider } + validates :uid, uniqueness: { scope: :provider } end diff --git a/db/migrate/20260731104506_add_unique_indexes_for_unique_validations.rb b/db/migrate/20260731104506_add_unique_indexes_for_unique_validations.rb new file mode 100644 index 000000000..f4c82f7c0 --- /dev/null +++ b/db/migrate/20260731104506_add_unique_indexes_for_unique_validations.rb @@ -0,0 +1,15 @@ +class AddUniqueIndexesForUniqueValidations < ActiveRecord::Migration[8.1] + disable_ddl_transaction! + + def change + add_index :auth_services, %i[uid provider], unique: true, algorithm: :concurrently + add_index :chapters, :name, unique: true, algorithm: :concurrently + add_index :chapters, :email, unique: true, algorithm: :concurrently + add_index :feedback_requests, %i[member_id workshop_id], unique: true, algorithm: :concurrently + add_index :feedback_requests, :token, unique: true, algorithm: :concurrently + add_index :invitations, %i[member_id event_id role], unique: true, algorithm: :concurrently + add_index :meeting_invitations, %i[member_id meeting_id], unique: true, algorithm: :concurrently + add_index :workshop_invitations, %i[member_id workshop_id role], unique: true, algorithm: :concurrently + add_index :workshop_sponsors, %i[sponsor_id workshop_id], unique: true, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index 37d4de123..9983a5607 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.1].define(version: 2026_07_29_151201) do +ActiveRecord::Schema[8.1].define(version: 2026_07_31_104506) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -75,6 +75,7 @@ t.string "uid" t.datetime "updated_at", precision: nil t.index ["member_id"], name: "index_auth_services_on_member_id" + t.index ["uid", "provider"], name: "index_auth_services_on_uid_and_provider", unique: true end create_table "bans", id: :serial, force: :cascade do |t| @@ -102,6 +103,8 @@ t.string "slug" t.string "time_zone", default: "London", null: false t.datetime "updated_at", precision: nil + t.index ["email"], name: "index_chapters_on_email", unique: true + t.index ["name"], name: "index_chapters_on_name", unique: true end create_table "chapters_events", id: :serial, force: :cascade do |t| @@ -242,6 +245,8 @@ t.integer "workshop_id" t.index ["member_id"], name: "index_feedback_requests_on_member_id" t.index ["workshop_id"], name: "index_feedback_requests_on_workshop_id" + t.index ["member_id", "workshop_id"], name: "index_feedback_requests_on_member_id_and_workshop_id", unique: true + t.index ["token"], name: "index_feedback_requests_on_token", unique: true end create_table "feedbacks", id: :serial, force: :cascade do |t| @@ -348,6 +353,7 @@ t.index ["member_id", "attending"], name: "index_invitations_member_attending" t.index ["member_id"], name: "index_invitations_on_member_id" t.index ["verified_by_id"], name: "index_invitations_on_verified_by_id" + t.index ["member_id", "event_id", "role"], name: "index_invitations_on_member_id_and_event_id_and_role", unique: true end create_table "jobs", id: :serial, force: :cascade do |t| @@ -389,6 +395,7 @@ t.index ["meeting_id"], name: "index_meeting_invitations_on_meeting_id" t.index ["member_id", "attending"], name: "index_meeting_invitations_member_attending" t.index ["member_id"], name: "index_meeting_invitations_on_member_id" + t.index ["member_id", "meeting_id"], name: "index_meeting_invitations_on_member_id_and_meeting_id", unique: true end create_table "meetings", id: :serial, force: :cascade do |t| @@ -599,6 +606,7 @@ t.index ["member_id", "attending"], name: "index_workshop_invitations_member_attending" t.index ["member_id"], name: "index_workshop_invitations_on_member_id" t.index ["token"], name: "index_workshop_invitations_on_token", unique: true + t.index ["member_id", "workshop_id", "role"], name: "idx_on_member_id_workshop_id_role_e3cea6bbfd", unique: true t.index ["workshop_id", "attending"], name: "index_workshop_invitations_workshop_attending" t.index ["workshop_id"], name: "index_workshop_invitations_on_workshop_id" end @@ -612,6 +620,7 @@ t.index ["sponsor_id"], name: "index_workshop_sponsors_on_sponsor_id" t.index ["workshop_id", "host"], name: "index_workshop_sponsors_on_workshop_id_and_host" t.index ["workshop_id"], name: "index_workshop_sponsors_on_workshop_id" + t.index ["sponsor_id", "workshop_id"], name: "index_workshop_sponsors_on_sponsor_id_and_workshop_id", unique: true end create_table "workshops", id: :serial, force: :cascade do |t|