2013-05-20 17 views
7

Tak, mam następujące:STI w Railsach: Jak zmienić nadklasę w podklasę bez bezpośredniego dostępu do atrybutu "type"?

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 

Jest to proces produkcji i montażu, w którym dane są przechwytywane o produktach. W momencie wychwytywania ostateczny rodzaj produktu nie jest znany. po utworzeniu rekordu produktu w bazie danych (być może kilka dni później) może być konieczne przekształcenie tego produktu w specjalistyczny produkt i uzupełnienie dodatkowych informacji. Jednak nie wszystkie produkty staną się wyspecjalizowane.

Próbowałem użyć następujących czynności:

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

Potem, kiedy robię SpecializedProduct.all otrzymany zestaw nie zawiera object_to_change. Zamiast object_to_change nadal figuruje w bazie danych jako 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]] 

Więc po wywołaniu .becomes SpecializedProduct metoda .save jest teraz przy użyciu odpowiedniego typu, ale nie jest w stanie zaktualizować rekord, ponieważ klauzula aktualizacji WHERE jest zbyt szczegółowe.

Czy naprawdę muszę uzyskać bezpośredni dostęp do atrybutu modelu type? Wolałbym nie.

+1

krótka odpowiedź: tak, musisz zaktualizować ją za pomocą typu. Ogólnie rzecz biorąc, STI nie jest przeznaczony do zmiany klas w czasie. –

Odpowiedz

0

Myślę, że nie! Sprawdź metody używane przez metody becomes i ! https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L199

Wygląda na to trzeba użyć becomes! ponieważ jest otoki wokół becomes że również zmienia wartość kolumny STI w instancji.

UPD:https://github.com/rails/rails/blob/4-0-stable/activerecord/test/cases/persistence_test.rb#L279 To jest przypadek testowy dla Twojego kodu.

UPD2: Myślę, że można spróbować utworzyć inną klasę rodzaju DefaultProject która jest podklasą Projektu, a następnie można zmienić każdy z DefaultProject do SpecializedProduct i vice versa

+1

Otrzymuję ten sam wynikowy SQL na konsoli rails z '.becomes!' Jak robię z '.becomes'' SET "type" =? 'Jest nadal tam, ale niestety tak samo jest" "products". "Type" IN ("SpecializedProduct") 'w klauzuli" WHERE ". – Jamie

+0

zobacz moją zaktualizowaną odpowiedź, powinna działać ... z której wersji rails korzystasz? – Fivell

1

Patrząc na źródło becomes i becomes!, nie zmienia oryginalnego obiektu. Trzeba przypisać go do nowej zmiennej:

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

Nie wiem, jak to będzie obsługiwać klucz podstawowy rekordu, choć, więc może trzeba zrobić kilka dodatkowych finagling upewnić się, że stosunki nie dostać oszpecony.

0

Mam podobny problem, gdy chcę zmienić jedną podklasę na inną. Niestety Rails nie robi tego z wdziękiem, ponieważ chce ograniczyć zapis z "where type =" NewSubclass "". tj:

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

kopanie w szynach, wydaje się, że metoda

w ActiveRecord za lib/active_record/inheritance.rb nazwie "finder_needs_type_condition?" jest wywoływana, a wywołujący nie jest wystarczająco inteligentny, aby zdać sobie sprawę, że zmieniasz pole typu, więc oczywiście nie jest to już ta wartość.

"Rozwiązałem" to w rundy wokół drogi.Użyłem rdzenia ActiveRecord jako podstawy do załadowania instancji dowolnej klasy, którą chcę z atrybutami i zapisania jej bez konieczności przechodzenia przez pełny stos odnajdywania ActiveRecord.

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 

To jest naprawdę brzydkie i nienawidzę tego. Mam nadzieję, że ktoś pomyśli o naprawieniu tego w ActiveRecord. Uważam, że byłoby użyteczne dla obiektów do zmiany podklas w STI w czasie. Mam jedną tabelę z 5 podklasami, ponieważ czyści ona model całkiem sporo.

To jedyna brzydota, z którą musiałem żyć. Pamiętaj, aby napisać odpowiednie testy, aby móc je wykryć, gdy ActiveRecord złamie to "obejście".

1

Po prostu potrzebujesz wersji bang metody stają się (!) I zapisać.

Różnica między tymi dwiema metodami: becomes tworzy nowe wystąpienie nowej klasy o tych samych wartościach atrybutu oryginalnego obiektu. becomes! aktualizuje również kolumnę typu.

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