Überprüfen Sie die Eindeutigkeit mehrerer Spalten

193

Gibt es eine Möglichkeit, zu überprüfen, ob ein tatsächlicher Datensatz eindeutig ist und nicht nur eine Spalte? Beispielsweise sollte ein Freundschaftsmodell / eine Freundschafts-Tabelle nicht mehrere identische Datensätze enthalten können, wie z.

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
re5et
quelle
7
vergib mir, wenn ich dicht bin, aber wie würde das in dieser Situation helfen?
Re5et
2
Versuchen Sie, "validates_uniqueness_of" in Ihrem Modell zu verwenden. Wenn dies nicht funktioniert, versuchen Sie, einen Index zu erstellen, für den Sie eine Migration von Feilds erstellen können, die eine Anweisung wie add_index: table, [: column_a ,: column_b] ,: unique => true) enthält
Harry Joy
1
@ HarryJoy, fragte er Is there a rails-way way. Und Sie bieten ihm Weg ohne Schienen, aber Standard. The Active Record way claims that intelligence belongs in your models, not in the database.
Grün
2
Leider validates :field_name, unique: trueist es anfällig für Rennbedingungen, so dass eine tatsächliche Einschränkung bevorzugt wird, obwohl dies gegen den Schienenweg erfolgt. @ HarryJoy Ich werde eine Antwort, die den Weg der Einschränkung beschreibt, positiv bewerten.
Pooyan Khosravi
1
Bessere Antwort als alles, was unten angegeben ist, ist dieses eine stackoverflow.com/a/34425284/1612469, da es eine weitere Ebene bringt, um sicherzustellen, dass alles richtig funktioniert
Aleks

Antworten:

319

Sie können einen validates_uniqueness_ofAnruf wie folgt ausführen.

validates_uniqueness_of :user_id, :scope => :friend_id
Dylan Markow
quelle
83
Ich wollte nur hinzufügen, dass Sie mehrere Bereichsparameter übergeben können, falls Sie die Eindeutigkeit in mehr als 2 Feldern überprüfen müssen. Dh: scope => [: friend_id ,: group_id]
Dave Rapin
27
Seltsam, dass man nicht sagen kann validates_uniqueness_of [:user_id, :friend_id]. Vielleicht muss dies gepatcht werden?
Alexey
12
Alexey, validates_uniqueness_of [: user_id ,: friend_id] wird nur die Validierung für jedes der aufgelisteten Felder durchführen - und es ist dokumentiert und erwartetes Verhalten
Nikita Hismatov
71
In Rails 4 wird dies: validiert: user_id, Eindeutigkeit: {scope :: friend_id}
Marina Martin
3
Sie möchten wahrscheinlich eine benutzerdefinierte Fehlermeldung hinzufügen, wie: message => 'hat bereits diesen Freund.'
Laffuste
137

Sie können eine Spalte validatesvalidieren uniqueness:

validates :user_id, uniqueness: {scope: :friend_id}

Die Syntax für die Validierung mehrerer Spalten ist ähnlich, Sie sollten jedoch stattdessen ein Array von Feldern angeben:

validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]}

Die oben gezeigten Validierungsansätze haben jedoch eine Rennbedingung und können keine Konsistenz gewährleisten. Betrachten Sie das folgende Beispiel:

  1. Datenbanktabellendatensätze sollen durch n Felder eindeutig sein ;

  2. Mehrere ( zwei oder mehr ) gleichzeitige Anforderungen, die jeweils von separaten Prozessen verarbeitet werden ( Anwendungsserver, Hintergrundarbeitsserver oder was auch immer Sie verwenden ), greifen auf die Datenbank zu, um denselben Datensatz in die Tabelle einzufügen.

  3. Jeder parallele Prozess überprüft, ob ein Datensatz mit denselben n Feldern vorhanden ist.

  4. Die Validierung für jede Anforderung wird erfolgreich bestanden, und jeder Prozess erstellt einen Datensatz in der Tabelle mit denselben Daten.

Um diese Art von Verhalten zu vermeiden, sollte der Datenbank-Tabelle eine eindeutige Einschränkung hinzugefügt werden . Sie können es mit dem add_indexHelfer für ein (oder mehrere) Felder festlegen, indem Sie die folgende Migration ausführen:

class AddUniqueConstraints < ActiveRecord::Migration
  def change
   add_index :table_name, [:field1, ... , :fieldn], unique: true
  end
end

Vorsichtsmaßnahme : Selbst nachdem Sie eine eindeutige Einschränkung festgelegt haben, versuchen zwei oder mehr gleichzeitige Anforderungen, dieselben Daten in db zu schreiben. Anstatt jedoch doppelte Datensätze zu erstellen, wird eine ActiveRecord::RecordNotUniqueAusnahme ausgelöst, die Sie separat behandeln sollten:

begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end 
Kali
quelle
2

Dies kann mit einer Datenbankeinschränkung für die beiden Spalten erfolgen:

add_index :friendships, [:user_id, :friend_id], unique: true

Sie könnten einen Rails-Validator verwenden, aber im Allgemeinen empfehle ich die Verwendung einer Datenbankeinschränkung.

Weitere Informationen: https://robots.thoughtbot.com/validation-database-constraint-or-both

Tate Thurston
quelle