2010-05-05 15 views
17

Ich habe ein Projektmodell, das verschachtelte Attribute für Task akzeptiert.validates_uniqueness_of in zerstörten verschachtelten Modellschienen

class Project < ActiveRecord::Base 
    has_many :tasks 

    accepts_nested_attributes_for :tasks, :allow_destroy => :true 

end 

class Task < ActiveRecord::Base 
validates_uniqueness_of :name end 

Eindeutigkeitsprüfung im Task-Modell verursacht ein Problem beim Aktualisieren des Projekts.

In Bearbeitung des Projekts lösche ich eine Aufgabe T1 und dann eine neue Aufgabe mit dem gleichen Namen T1 hinzufügen, Eindeutigkeit Validierung beschränkt das Speichern von Project.

params-Hash-Look etwas wie

task_attributes => { {"id" => 
"1","name" => "T1", "_destroy" => 
"1"},{"name" => "T1"}} 

Validierung auf Aufgabe erfolgt vor die alte Aufgabe zu zerstören. Daher schlägt die Validierung fehl. Eine Idee, wie man validiert, dass die Aufgabe nicht zerstört wird?

+0

Nur neugierig Warum nicht aktualisieren Sie Ihre alte Aufgabe, anstatt alte zu löschen und neue Aufgabe mit dem gleichen Namen erstellen. – Salil

+0

Sie meinen, ich muss alte Aufgaben durchlaufen und prüfen, ob es eine alte Aufgabe mit dem gleichen Namen wie eine neue Aufgabe gibt, die aber als zerstört markiert ist, und dann nur diese alte Aufgabe aktualisieren? – arun

+0

Arun ... ist dies nur ein Testfall (Hinzufügen einer Aufgabe mit dem gleichen Namen wie eine andere Aufgabe, die Sie löschen) oder machen Sie dies bei jeder Bearbeitung, dh Aufgaben löschen und neu erstellen. – concept47

Antwort

13

Andrew Frankreich erstellt einen Patch in diesem thread, wo die Validierung im Speicher erfolgt.

class Author 
    has_many :books 

    # Could easily be made a validation-style class method of course 
    validate :validate_unique_books 

    def validate_unique_books 
    validate_uniqueness_of_in_memory(
     books, [:title, :isbn], 'Duplicate book.') 
    end 
end 

module ActiveRecord 
    class Base 
    # Validate that the the objects in +collection+ are unique 
    # when compared against all their non-blank +attrs+. If not 
    # add +message+ to the base errors. 
    def validate_uniqueness_of_in_memory(collection, attrs, message) 
     hashes = collection.inject({}) do |hash, record| 
     key = attrs.map {|a| record.send(a).to_s }.join 
     if key.blank? || record.marked_for_destruction? 
      key = record.object_id 
     end 
     hash[key] = record unless hash[key] 
     hash 
     end 
     if collection.length > hashes.length 
     self.errors.add_to_base(message) 
     end 
    end 
    end 
end 
+1

Danke RainerB, das löst mein Problem. – arun

+3

add_to_base wurde eingestellt und ist in 3.1 nicht mehr verfügbar. Verwenden Sie self.errors.add (: base, message) –

+1

Heh, ich habe eine E-Mail von jemandem erhalten, der gesagt hat, dass er meine Problemumgehung über StackOverflow gefunden hat. Schön zu wissen, dass es Leuten geholfen hat. Vielleicht sollte ich es für Rails 3 aktualisieren, da diese Implementierung heutzutage als sehr schlecht angesehen würde! –

-3

Ref this

Warum Sie nicht verwenden: scope

class Task < ActiveRecord::Base 
    validates_uniqueness_of :name, :scope=>'project_id' 
end 

dies einzigartige Aufgabe für jedes Projekt erstellen.

+0

scope => 'project_id' funktioniert für verschiedene Projekt-IDs, aber im obigen Fall ist project_id gleich. Ich muss eine Aufgabe zerstören und dann eine neue Aufgabe mit demselben Namen für ein Projekt hinzufügen. – arun

+0

werden Sie bitte Ihren Code hier einfügen, damit man Ihnen helfen kann. – Salil

+0

In Controller aktualisieren def @project = Project.find (params [: id]) @ project.update_attributes (params [: Projekt]) Ende Modell-Code ist gleich wie i above.I genau erwähnt haben getan haben ähnlich wie beschrieben von Ryan Bates in Railscasts Episode 197 – arun

1

Wie ich es verstehe, Reiner Ansatz etwa in Speicher Validierung wäre nicht in meinem Fall sinnvoll sein, da ich viele „Bücher“ haben, 500K und wächst. Das wäre ein großer Erfolg, wenn Sie alle in Erinnerung bringen möchten.

Die Lösung kam ich mit ist:

Legen Sie die Einzigartigkeit Zustand in der Datenbank (die ich gefunden habe, ist immer eine gute Idee, wie es in meiner Erfahrung Rails nicht immer einen guten Job hier tun) folgendes Migrationsdatei in db durch Hinzufügen/Migration /:

add_index :tasks [ :project_id, :name ], :unique => true 

in der Steuerung legt die speichern oder update_attributes innerhalb einer Transaktion, und die Datenbank Ausnahme retten. ZB

def update 
    @project = Project.find(params[:id]) 
    begin 
    transaction do  
     if @project.update_attributes(params[:project]) 
      redirect_to(project_path(@project)) 
     else 
     render(:action => :edit) 
     end 
    end 
    rescue 
    ... we have an exception; make sure is a DB uniqueness violation 
    ... go down params[:project] to see which item is the problem 
    ... and add error to base 
    render(:action => :edit) 
    end 
end 

Ende

+0

Ausgezeichnete Ansatz! Diese Antwort bietet die beste Lösung, IMO. Robust, schnell, ausfallsicher und zuverlässig. Datenbankindizes können nicht lügen. ;) – mycargus

1

Für Rails 4.0.1 ist dieses Problem gekennzeichnet durch diese Pull-Anforderung festgelegt ist, https://github.com/rails/rails/pull/10417

Wenn Sie eine Tabelle mit einem eindeutigen Feldindex haben, und Sie markieren einen Datensatz für die Zerstörung, und Sie erstellen einen neuen Datensatz mit dem gleichen Wert wie das eindeutige Feld , wenn Sie dann aufrufen, wird eine Datenbankebene eindeutigen Index Fehler ausgelöst werden.

Persönlich funktioniert das immer noch nicht für mich, also glaube ich nicht, dass es noch vollständig behoben ist.

1

Rainer Blessings Antwort ist gut. Aber es ist besser, wenn wir markieren können, welche Aufgaben dupliziert werden.

Verwandte Themen