2013-05-20 5 views
7

Also, ich habe folgendes:STI in Rails: Wie kann ich von einer Oberklasse zu einer Unterklasse wechseln, ohne direkt auf das Attribut "type" zuzugreifen?

class Product < ActiveRecord::Base 
    # Has a bunch of common stuff about assembly hierarchy, etc 
end 

class SpecializedProduct < Product 
    # Has some special stuff that a "Product" can not do! 
end 

Es gibt eine Fertigungs- und Montageverfahren, bei dem Daten über Produkte erfasst wird. Zum Zeitpunkt der Erfassung ist der eventuelle Produkttyp nicht bekannt. Nachdem der Produktdatensatz in der Datenbank erstellt wurde (vielleicht einige Tage später), kann es notwendig sein, das Produkt in ein spezielles Produkt umzuwandeln und die zusätzlichen Informationen einzugeben. Nicht alle Produkte werden sich jedoch spezialisieren.

Ich habe versucht, die folgenden zu verwenden:

object_to_change = Product.find(params[:id]) 
object_to_change.becomes SpecializedProduct 
object_to_change.save 

Dann, wenn ich eine SpecializedProduct.all die resultierende Menge tun nicht enthalten object_to_change. Stattdessen wird object_to_change noch in der Datenbank als Product

UPDATE "products" SET "type" = ?, "updated_at" = ? WHERE "products"."type" IN ('SpecializedProduct') AND "products"."id" = 30 [["type", "Product"], ["updated_at", Fri, 17 May 2013 10:28:06 UTC +00:00]] 

Also, nach dem Aufruf von .becomes SpecializedProduct die .save Methode jetzt der richtige Art verwendet, aber es ist nicht in der Lage, den Datensatz zu aktualisieren, da die WHERE Klausel des Updates zu spezifisch.

Muss ich wirklich auf das type Attribut des Modells direkt zugreifen? Ich würde es wirklich nicht tun.

+1

kurze Antwort: Ja, Sie müssen es mit Typ aktualisieren. Im Allgemeinen ist STI nicht dafür ausgelegt, Klassen im Laufe der Zeit zu ändern. –

Antwort

0

Ich denke nein! Überprüfen Sie die Quellen von becomes und becomes! Methoden! https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L199

Sieht aus wie Sie becomes! verwenden müssen, weil es Wrapper um ist becomes, dass auch die sti Spalte Wert der Instanz ändert.

UPD:https://github.com/rails/rails/blob/4-0-stable/activerecord/test/cases/persistence_test.rb#L279 Dies ist ein Testfall für Ihren Code.

UPD2: Ich glaube, Sie können versuchen, eine andere Klasse Art von DefaultProject zu schaffen, die Unterklasse von Project ist und dann können Sie die jeweils von DefaultProject zu SpecializedProduct und umgekehrt

+1

Ich bekomme die gleiche resultierende SQL auf der Rails-Konsole mit '.becomes!' Wie ich mit '.becomes'' SET "Typ" =? 'Ist immer noch drin, aber leider ist das auch so" "products". "Type" IN ("SpecializedProduct") in der WHERE-Klausel. – Jamie

+0

sehe meine aktualisierte Antwort, es sollte funktionieren ... welche Schienen Version verwenden Sie? – Fivell

1

Mit Blick auf die Quelle der becomes ändern und becomes!, es mutiert nicht das ursprüngliche Objekt. Sie müssen sie eine neue Variable zuzuweisen:

some_product = Product.find(params[:id]) 
specialized_product = some_product.becomes SpecializedProduct 
specialized_product.save 

nicht sicher, wie dies die Primärschlüssel des Datensatzes handhaben, obwohl, so müssen Sie möglicherweise einige zusätzliche finagling tun, um Ihre Beziehungen, um sicherzustellen, nicht bekommen, verstümmelt.

0

Ich habe ein ähnliches Problem, wo ich von einer Unterklasse zu einer anderen wechseln möchte. Leider macht Rails das nicht anmutig, weil es das Speichern mit einem "where type = 'NewSubclass'" begrenzen möchte. dh:

UPDATE parents SET type='NewSubclass' WHERE type IN 'NewSubclass' AND id=1234 

Graben in Schienen, scheint es, dass das Verfahren in Active lib/active_record/inheritance.rb namens "finder_needs_type_condition?" aufgerufen wird und der Aufrufer nicht schlau genug ist zu erkennen, dass Sie das Typfeld ändern, so ist es offensichtlich nicht schon dieser Wert.

Ich "gelöst" dies in einer Runde über.Ich habe den ActiveRecord-Kern als Grundlage dafür verwendet, wie ich eine Instanz der gewünschten Klasse mit Attributen laden und speichern kann, ohne den gesamten ActiveRecord-Find-Stack durchlaufen zu müssen.

old_instance = Parent.find(id) #returns OldSubclass instance 
tmp = Parent.allocate.init_with('attributes' => old_instance.attributes) 
tmp.type = 'NewSubclass' 
tmp.save 
Parent.find(id) #returns NewSubclass instance 

Es ist wirklich hässlich und ich hasse es. Hoffentlich denkt jemand darüber nach, dies in ActiveRecord zu beheben. Ich glaube, dass es für Objekte nützlich wäre, Subklassen in STI im Laufe der Zeit zu ändern. Ich habe eine einzige Tabelle mit 5 Unterklassen, weil sie das Modell ziemlich aufräumt.

Das ist die einzige Hässlichkeit, mit der ich leben musste. Stellen Sie sicher, dass Sie die richtigen Tests schreiben, damit ActiveRecord diese "Problemumgehung" durchbrechen kann.

1

Sie brauchen nur die Bang-Version der Methode wird mit (!) Und speichern. Der Unterschied zwischen den beiden Methoden: becomes erstellt eine neue Instanz der neuen Klasse mit den gleichen Attributwerten des ursprünglichen Objekts. becomes! aktualisiert auch die Typspalte.

object_to_change = Product.find(params[:id]) 
object_to_change.becomes! SpecializedProduct 
object_to_change.save 
Verwandte Themen