2013-05-20 15 views
7

Così, ho il seguente:STI in Rails: come passare da una superclasse a una sottoclasse senza accedere direttamente all'attributo "tipo"?

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 

C'è un processo di fabbricazione e di assemblaggio in cui i dati vengono catturati su Prodotti. Al momento della cattura, l'eventuale tipo di prodotto non è noto. dopo che il record del Prodotto è stato creato nel database (forse alcuni giorni dopo) potrebbe essere necessario trasformare quel prodotto in un prodotto specializzato e inserire le informazioni aggiuntive. Tuttavia, non tutti i prodotti diventeranno specializzati.

Ho cercato di utilizzare il seguente:

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

Poi, quando faccio una SpecializedProduct.all l'insieme risultante non include object_to_change. Invece object_to_change è ancora elencato nel database come 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]] 

Così, dopo la chiamata al .becomes SpecializedProduct il metodo .save ora sta usando il tipo giusto, ma non è in grado di aggiornare il record perché la clausola WHERE dell'aggiornamento è troppo specifico

Devo davvero accedere direttamente all'attributo type del modello? Preferirei davvero di no.

+1

risposta breve: sì, è necessario aggiornarlo con il tipo. In generale, STI non è progettato per cambiare classe nel tempo. –

risposta

0

Penso di no! Controlla i vantaggi dei metodi becomes e becomes!! https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L199

Sembra che è necessario utilizzare becomes! perché è wrapper becomes che cambia anche il valore della colonna sti dell'istanza.

UPD:https://github.com/rails/rails/blob/4-0-stable/activerecord/test/cases/persistence_test.rb#L279 Si tratta di un banco di prova per il vostro codice.

UPD2: Penso che si può provare a creare un'altra classe sorta di DefaultProject che è sottoclasse di progetto e poi si può cambiare ogni da DefaultProject a SpecializedProduct e viceversa

+1

Ottengo lo stesso SQL risultante nella console rails con '.becomes!' Come faccio con '.becomes'. SET" type "=?' È ancora lì, ma sfortunatamente lo è anche "prodotti". "Type" IN ('SpecializedProduct') 'nella clausola' WHERE'. – Jamie

+0

vedi la mia risposta aggiornata, dovrebbe funzionare ... quale versione di rails stai usando? – Fivell

1

Guardando la fonte di becomes e becomes!, non muta l'oggetto originale. È necessario assegnarlo ad una nuova variabile:

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

sicuro di come questo si occuperà la chiave primaria del record, però, quindi potrebbe essere necessario fare un po 'finagling aggiuntivo per assicurarsi che le relazioni non ottengono maciullato.

0

Ho un problema simile in cui desidero passare da una sottoclasse all'altra. Sfortunatamente Rails non lo fa con grazia perché vuole limitare il salvataggio con "where type = 'NewSubclass'". vale a dire:

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

Scavando nelle guide, sembra che il metodo in lib/active_record/inheritance.rb nome di ActiveRecord "finder_needs_type_condition?" viene chiamato e il chiamante non è abbastanza intelligente da capire che stai cambiando il campo tipo, quindi ovviamente non è già quel valore.

"Ho risolto" questo in modo approssimativo.Ho usato il core ActiveRecord come base per caricare un'istanza di qualsiasi classe che desidero con gli attributi e salvarla senza dover passare attraverso lo stack di ricerca completo di 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 

È davvero brutto e lo odio. Spero che qualcuno pensi a sistemarlo in ActiveRecord. Credo che sarebbe utile che gli oggetti cambiassero sottoclassi in STI nel tempo. Ho un tavolo singolo con 5 sottoclassi perché pulisce un po 'il modello.

Questa è l'unica bruttezza con cui ho dovuto convivere. Assicurati di scrivere test appropriati in modo che quando ActiveRecord rompe questa "soluzione alternativa" puoi rilevarlo.

1

Hai solo bisogno della versione bang del metodo diventa con (!) E salva.

La differenza tra i due metodi: becomes crea una nuova istanza della nuova classe con tutti gli stessi valori di attributo dell'oggetto originale. becomes! aggiorna anche la colonna del tipo.

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