Bėgiai: kaip nustatyti unikalų keičiamo indekso apribojimą

Nustatydami unikalumo patvirtinimą bėgiais galų gale atliksite gana dažnai. Galbūt jūs juos jau pridėjote prie daugumos savo programų. Tačiau šis patvirtinimas suteikia tik gerą vartotojo sąsają ir patirtį. Jis informuoja vartotoją apie klaidas, trukdančias duomenims išlikti duomenų bazėje.

Kodėl nepakanka unikalumo patvirtinimo

Net patvirtinus unikalumą, nepageidaujami duomenys kartais išsaugomi duomenų bazėje. Aiškumo dėlei pažvelkime į žemiau pateiktą vartotojo modelį:

class User validates :username, presence: true, uniqueness: true end 

Norėdami patvirtinti vartotojo vardo stulpelį, bėgiai pateikia duomenų bazės užklausas naudodami „SELECT“, kad patikrintų, ar vartotojo vardas jau yra. Jei taip, tai atspausdina „Vartotojo vardas jau yra“. Jei taip nėra, ji paleidžia INSERT užklausą, kad duomenų bazėje išliktų naujas vartotojo vardas.

Kai du vartotojai tuo pačiu metu vykdo tą patį procesą, duomenų bazė kartais gali išsaugoti duomenis, neatsižvelgdama į patvirtinimo apribojimus ir būtent tada atsiranda duomenų bazės apribojimai (unikalus indeksas).

Jei vartotojas A ir vartotojas B bando išsaugoti tą patį vartotojo vardą duomenų bazėje tuo pačiu metu, bėgiai paleidžia užklausą SELECT, jei vartotojo vardas jau yra, jis informuoja abu vartotojus. Tačiau jei vartotojo vardo duomenų bazėje nėra, jis vienu metu paleidžia INSERT užklausą abiem vartotojams, kaip parodyta paveikslėlyje žemiau.

Dabar, kai žinote, kodėl duomenų bazės unikalus indeksas (duomenų bazės apribojimas) yra svarbus, pasidomėkime, kaip jį nustatyti. Gana paprasta nustatyti duomenų bazės unikalų indeksą (-us) bet kuriam stulpeliui ar stulpelių rinkiniui bėgiuose. Tačiau kai kurie duomenų bazės apribojimai bėgiuose gali būti keblūs.

Greita apžvalga nustatant unikalų indeksą vienam ar daugiau stulpelių

Tai yra taip pat paprasta, kaip paleisti migraciją. Tarkime, kad turime naudotojų lentelę su stulpelio vartotojo vardu ir norime užtikrinti, kad kiekvienas vartotojas turėtų unikalų vartotojo vardą. Tiesiog sukurkite perkėlimą ir įveskite šį kodą:

add_index :users, :username, unique: true 

Tada paleisite migraciją ir viskas. Dabar duomenų bazė užtikrina, kad lentelėje nebūtų išsaugoti jokie panašūs vartotojo vardai.

Tarkime, kad turime kelis susietus stulpelius, turinčius užklausų lentelę su stulpeliais sender_id ir receiver_id. Panašiai tiesiog sukurkite perkėlimą ir įveskite šį kodą:

add_index :requests, [:sender_id, :receiver_id], unique: true 

Štai ir viskas? Oi, ne taip greitai.

Aukščiau nurodytos kelių stulpelių perkėlimo problema

Problema ta, kad šiuo atveju ID yra keičiami. Tai reiškia, kad jei jūsų siuntėjo_id yra 1 ir gavėjo_id yra 2, užklausų lentelėje vis tiek galima išsaugoti siuntėjo_id 2 ir gavėjo ID 1, net jei jie jau laukia užklausos.

Ši problema dažnai įvyksta susiejant save. Tai reiškia, kad tiek siuntėjas, tiek imtuvas yra vartotojai, o siuntėjo ID arba gavėjo ID nurodomi iš vartotojo_id. Vartotojas, kurio vartotojo ID (siuntėjo_id) yra 1, siunčia užklausą vartotojui, kurio vartotojo ID (gavėjo ID) yra 2.

Jei imtuvas vėl siunčia kitą užklausą ir mes leidžiame jai išsaugoti duomenų bazėje, tada užklausų lentelėje turime du panašius prašymus iš tų pačių dviejų vartotojų (siuntėjo ir gavėjo || imtuvo ir siuntėjo).

Tai iliustruojama žemiau esančiame paveikslėlyje:

Bendras pataisymas

Ši problema dažnai išsprendžiama naudojant žemiau esantį pseudo kodą:

def force_record_conflict # 1. Return if there is an already existing request from the sender to receiver # 2. If not then swap the sender and receiver end 

Šio sprendimo problema yra ta, kad gavėjo_id ir siuntėjo_ID kiekvieną kartą keičiamos prieš išsaugant duomenų bazėje. Taigi stulpelyje imtuvas_ID reikės išsaugoti siuntėjo_id ir atvirkščiai.

Pvz., Jei vartotojas, kurio siuntėjo ID yra 1, siunčia užklausą vartotojui, kurio gavėjo ID yra 2, užklausų lentelė bus tokia, kaip parodyta žemiau:

Tai gali atrodyti ne problema, bet geriau, jei jūsų stulpeliai išsaugo tikslius duomenis, kuriuos norite išsaugoti. Tai turi daug privalumų. Pvz., Jei jums reikia nusiųsti pranešimą imtuvui per imtuvo_ID, tada duomenų bazėje teiksite užklausą dėl tikslaus ID iš stulpelio imtuvas_ID. Tai jau tapo painiau tuo momentu, kai pradėjote keisti užklausos lentelėje išsaugotus duomenis.

Tinkamas taisymas

This problem can be entirely resolved by talking to the database directly. In this case, I’ll explain using PostgreSQL. When running the migration, you must ensure that the unique constraint checks for both (1,2) and (2,1) in the request table before saving.

You can do that by running a migration with the code below:

class AddInterchangableUniqueIndexToRequests < ActiveRecord::Migration[5.2] def change reversible do |dir| dir.up do connection.execute(%q( create unique index index_requests_on_interchangable_sender_id_and_receiver_id on requests(greatest(sender_id,receiver_id), least(sender_id,receiver_id)); create unique index index_requests_on_interchangable_receiver_id_and_sender_id on requests(least(sender_id,receiver_id), greatest(sender_id,receiver_id)); )) end dir.down do connection.execute(%q( drop index index_requests_on_interchangable_sender_id_and_receiver_id; drop index index_requests_on_interchangable_receiver_id_and_sender_id; )) end end end end 

Code explanation

After creating the migration file, the reversible is to ensure that we can revert our database whenever we must. The dir.up is the code to run when we migrate our database and dir.down will run when we migrate down or revert our database.

connection.execute(%q(...)) is to tell rails that our code is PostgreSQL. This helps rails to run our code as PostgreSQL.

Since our “ids” are integers, before saving into the database, we check if the greatest and least (2 and 1) are already in the database using the code below:

requests(greatest(sender_id,receiver_id), least(sender_id,receiver_id)) 

Tada mes taip pat patikriname, ar mažiausia ir didžiausia (1 ir 2) yra duomenų bazėje, naudodami:

requests(least(sender_id,receiver_id), greatest(sender_id,receiver_id)) 

Tada užklausų lentelė bus tiksliai tokia, kokią ketiname, kaip parodyta žemiau esančiame paveikslėlyje:

Štai ir viskas. Laimingo kodavimo!

Nuorodos:

Edgeguides | Thoughtbot