2015-10-27 3 views
9

Ich habe eine Situation gefunden, in der ActiveRecord scheinbar überflüssige Kinderdatensätze überprüft. Entschuldigung im Voraus für die Länge, da dies ziemlich komplex ist.ActiveRecord scheint unnötige Änderungen der untergeordneten Datensätze zu bestätigen

Hierbei handelt es sich um Verknüpfungen, die zuvor verwendet, aber in keiner Weise geändert wurden. Es tritt bei 3.2 bis zum letzten Master auf. Ich bin mir nicht sicher, ob es eine Design-Entscheidung ist, die zu unerwartetem Verhalten oder einem Fehler irgendeiner Art geführt hat.

ich einen Testfall aus dem eigentlichen Code reduziert sich wie folgt:

Modelle:

class A < ActiveRecord::Base 
    belongs_to :b 
    has_many :cs, :through => :b 
    before_validation { puts "A" } 
end 

class B < ActiveRecord::Base 
    has_many :as 
    has_many :cs 
    before_validation { puts "B" } 
end 

class C < ActiveRecord::Base 
    belongs_to :b 
    before_validation { puts "C" } 
end 

Migration:

class AddABC < ActiveRecord::Migration 
    def change 
    create_table :as do |t| 
     t.references :b 
    end 

    create_table :bs do |t| 
    end 

    create_table :cs do |t| 
     t.references :b 
    end 
    end 
end 

Der reduzierte Testfall, der es auslöst, ist dies bei der Ausführung in einer leeren Datenbank:

b = B.create! 
c = C.create! 
b.cs << c 
a = A.new 
a.b = b 
a.cs.first 
puts "X" 
a.valid? 

die Ausgabe gibt:

B 
C 
C 
X 
A 
C 

Welche, dass ein A seine Cs validiert Validierung zeigt.

Nun nachdem ich in diese ich die Option has_many :validate => false berücksichtigt habe, und das Problem damit verschwindet. Aber mir scheint, dass hier mehr vor sich geht als hier - ertragen Sie mit mir.

Die AR docs say:

: validieren Wenn falsch, nicht die zugehörigen Objekte überprüfen, wenn das übergeordnete Objekt zu speichern. standardmäßig true

Aber ich finde das verwirrend, da dies eindeutig nicht alle Datensätze bedeuten kann. Es wird die Objekte nicht validieren, wenn ich nie die Verbindung bekomme (entferne a.cs.first aus dem obigen Code), oder ich bekomme es, aber benutze es nie (ersetze es mit a.cs). Dies ist, weil es durch validate_collection_association in lib/active_record/autosave_association.rb geht, die den Code enthält:

def validate_collection_association(reflection) 
    if association = association_instance_get(reflection.name) 
     if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave]) 
     records.each_with_index { |record, index| association_valid?(reflection, record, index) } 
     end 
    end 
    end 

Es ist alles davon abhängig association_instance_get die von dem Verband Cache abruft. Kein Cache bedeutet, dass keine Datensätze zu validieren sind.

Ich habe versucht, eine einfachere has_many zu machen, indem ich nur ein B-Modell aufstellt, das A referenziert, aber dann muss ich das B vor dem A erstellen, dann wird A nicht mehr ein neuer Datensatz sein, wenn ich es versuche es, und dieser Code verhindert, dass das Problem, da der Zweig speichern nicht mehr genannt wird die erste sein:

def associated_records_to_validate_or_save(association, new_record, autosave) 
    if new_record 
     association && association.target 
    elsif autosave 
     association.target.find_all(&:changed_for_autosave?) 
    else 
     association.target.find_all(&:new_record?) 
    end 
    end 

die einzige wirkliche Erklärung, die ich mit oben kommen kann für sie nur geladene Datensätze validiert ist, weil die Absicht von Active hier dient nur dazu, geänderte Datensätze zu validieren. Wirklich würde ich erwarten, dass es validiert, wenn und nur wenn es sparen wird, und folglich die Standardautosave-Option, nur geänderte Aufzeichnungen zu speichern, eine Gültigkeitsprüfung verhindern sollte.

Ich habe eine related ticket gefunden und die commit 27aa4dda7d89ce733 (noch nicht in irgendeiner Version denke ich), die eine Änderung vornimmt, aber dieses spezifische Problem von meinen Tests nicht behebt.Es enthält jedoch den Ausdruck:

!record.persisted? || record.changed? || record.marked_for_destruction? 

und wenn ich diesen Zustand zu der innersten Schleife von validate_collection_association fügen Sie dann das Problem verschwindet, wobei die Active Tests immer noch auf meiner Maschine vorbei.

Dies war ein bedeutendes Leistungsproblem in meinem Projekt, weil das fragliche Modell nur im Admin validiert werden sollte, wo ein nicht indizierten Feld in einer benutzerdefinierten Validierung akzeptiert wurde aufgrund der Seltenheit der Speicherung und damit ich beurteilten, dass die Indizierung überindizieren würde (es wäre nicht nur ein Feld). Offensichtlich ist diese Übervalidierung in den meisten Fällen viel weniger ernst und scheint nur in einem speziellen Fall zu passieren, so dass dies ein Fehler sein könnte.

So, während ich eine gute Idee habe, was passiert, bin ich nicht ganz sicher, was passieren sollte, weshalb ich dies nicht als ActiveRecord Ticket eingereicht habe. Denkst du, das ist ein Fehler? Warum funktioniert es so? Wofür ist die Validierungsoption wirklich? Wenn das ein Fehler ist, können Sie erklären, warum der Code so funktioniert und warum er übergreift? In welchem ​​Fall würde mein Code zu ActiveRecord über Break wechseln?

+0

Ich wäre interessiert zu sehen, wenn Hinzufügen von inverse_of: auf den Assoziationen dieses Verhalten überhaupt geändert. TBH Ich habe aber keine große Hoffnung, dass es so wäre. –

+0

In meiner Prüfung tat es nicht. –

Antwort

3

Der Grund, warum dies geschieht, ist, weil die Beziehung zwischen A und C durch B. ist

Bevor Sie a.b = b zuweisen, hat a keine bs oder cs.

Wenn Sie a.b = b zuweisen, aber Sie a.cs nicht nennen, dann hat a keinen Grund, zu versuchen, den zugehörigen cs zu laden. has_many erstellt nur die cs Convenience-Methode, sie wird nicht für Sie aufgerufen. Hier wird nur a.b_id auf b.id gesetzt.

Sobald Sie rufen a.cs, a für zugehörige cs Objekte durch b seit b aussehen verfügbar ist. Es findet diese Objekte und fügt sie als untergeordnete Objekte zu a hinzu.

Ich sehe Ihren Punkt, dass technisch, gibt es nichts zu tun in diesem speziellen Fall in diesem speziellen Schema für die cs, aber ich kann sehen, warum prüft. Diese Objekte, so weit es betroffen ist, sind Kinder von a und Kinderaufzeichnungen sind validiert, sofern nicht ausdrücklich angegeben, nicht durch validate: false.

In diesem Fall ist a ein Kind von b, daher ist a nicht erforderlich, um es zu validieren.

Im Allgemeinen werden die Eltern die zugehörigen Kinder validieren lassen. Kinder müssen ihre Eltern nicht validieren.

Verwandte Themen