2012-03-07 13 views
43

Ho i seguenti 2 modelliRails after_initialize solo sui "nuovi"

class Sport < ActiveRecord::Base 
    has_many :charts, order: "sortWeight ASC" 
    has_one :product, :as => :productable 
    accepts_nested_attributes_for :product, :allow_destroy => true 
end 

class Product < ActiveRecord::Base 
    belongs_to :category 
    belongs_to :productable, :polymorphic => true 
end 

Uno sport non può esistere senza il prodotto, così nel mio sports_controller.rb ho avuto:

def new 
    @sport = Sport.new 
    @sport.product = Product.new 
... 
end 

ho cercato di spostare la creazione del prodotto per il modello sportivo, utilizzando after_initialize:

after_initialize :create_product 

def create_product 
self.product = Product.new 
end 

ho imparato in fretta t il cappello after_initialize viene chiamato ogni volta che viene creato un modello (ad esempio, da una chiamata find). Quindi non era questo il comportamento che stavo cercando.

Qual è il modo in cui dovrei modellare il requisito che tutti gli sport hanno uno product?

Grazie

risposta

57

Inserire la logica nel controller potrebbe essere la migliore risposta come hai affermato, ma potresti fare in modo che after_initialize funzioni come segue:

after_initialize :add_product 

def add_product 
    self.product ||= Product.new 
end 

In questo modo, imposta il prodotto solo se non esiste alcun prodotto. Potrebbe non valere l'overhead e/o essere meno chiaro di avere la logica nel controller.

Edit: La risposta di Per Ryan, prestazioni-saggio il seguente sarebbe probabilmente meglio:

after_initialize :add_product 

def add_product 
    self.product ||= Product.new if self.new_record? 
end 
+0

Il problema che ho usato con questa soluzione è che nel mio caso il campo prodotto può essere nullo (quindi ho davvero bisogno di after_initialize solo su create) .. se qualcuno ha un'idea, sarebbe fantastico, grazie! – yorch

+1

@yorch Controlla prima/dopo_creare. Se la tua logica è troppo complicata, probabilmente non vuoi nasconderla in un hook before/after perché potrebbe essere troppo * magico *. – bostonou

+0

Questo metodo tuttavia non è ottimizzato, una volta che inizi a selezionare una grande quantità di sport/prodotti, vedrai che la tua query è molto poco ottimizzata perché per ogni relazione hai una dichiarazione selezionata per vedere se il prodotto esiste – YaBoyQuy

2

Invece di usare after_initialize, come su after_create?

after_create :create_product 

def create_product 
    self.product = Product.new 
    save 
end 

Sembra che risolverebbe il problema?

+3

Questo è per il metodo 'new' nel controller .... nulla viene salvato nel db ancora, quindi' after_create' non sarà chiamato. –

1

Sembra che tu sia molto vicino. Dovresti essere in grado di eliminare del tutto la chiamata after_initialize, ma prima credo che se il tuo modello Sport ha una relazione "has_one" con: prodotto come hai indicato, allora il tuo modello di prodotto dovrebbe anche "appartenere allo sport". Aggiungi questo al vostro modello di prodotto

belongs_to: :sport 

Il passo successivo, si dovrebbe ora essere in grado di creare un'istanza di un modello di sport in questo modo

@sport = @product.sport.create(...) 

Questo si basa fuori le informazioni da Association Basics da Ruby on Rails Guide, che potresti avere una lettura se non sono esattamente corretto

+0

L'associazione polimorfica elimina un po 'questo aspetto. Un prodotto appartiene a un prodotto (parola composta). Questo perché alcuni prodotti sono sportivi e alcuni sono film (sto iniziando con gli sport). Questa è la mia comprensione di come gestire l'ereditarietà di Rails (oltre all'ereditarietà della tabella singola, che non volevo). Grazie comunque per l'idea! –

+0

Ah capisco, non mi ero reso conto della natura polimorfa del modello e, a sua volta, ho realizzato che non so come gestirlo. Continuerò a guardarlo anche da solo. Ti farò sapere se mi viene in mente qualcosa. – coderates

+0

Questo [railscast] (http://railscasts.com/episodes/154-polymorphic-association) sembra contenere la risposta che stai cercando. Sembra che tu usi un modo simile di creare il nuovo sport come ho tentato sopra. Spero che questo ti aiuti. – coderates

25

Se lo fate self.product ||= Product.new sarà ancora cercare un prodotto ogni volta che fate un find perché ha bisogno di controlla se è nullo o no. Di conseguenza non farà alcun carico impaziente. Per fare ciò solo quando viene creato un nuovo record, è sufficiente verificare se si tratta di un nuovo record prima di impostare il prodotto.

after_initialize :add_product 

def add_product 
    self.product ||= Product.new if self.new_record? 
end 

ho fatto qualche analisi comparativa di base e il controllo if self.new_record? non sembra influire sulle prestazioni in alcun modo evidente.

+0

Ottimo lavoro con la performance colpita invocando new_record? ! – conciliator

+12

È anche possibile spostare il controllo 'new_record?' Da 'add_product' scrivendo' after_initialize: add_product,: if =>: new_record? '. In alcuni casi sarà meglio organizzato. –

0

Si dovrebbe solo eseguire l'override inizializzare metodo come

class Sport < ActiveRecord::Base 

    # ... 

    def initialize(attributes = {}) 
    super 
    self.build_product 
    self.attributes = attributes 
    end 

    # ... 

end 

metodo Initialize viene mai chiamato quando record viene caricato dal database. Si noti che nel codice sopra gli attributi vengono assegnati dopo che il prodotto è stato creato. In tale attributo l'assegnazione degli attributi può influire sull'istanza del prodotto creata.

+1

Non dovresti farlo per una serie di ragioni dettagliate qui: http://stackoverflow.com/questions/4376992/how-to-override-new-method-for-a-rails-model – tirdadc

+0

No, non c'è motivo non sovrascrivere inizializza. Contrariamente, se si esamina il codice sorgente dei binari, capirai che l'inizializzazione era destinata a essere sovrascritta. Tutto quello che devi fare è chiamare super nella tua inizializzazione sovrascritta, ma è una regola che dovrebbe essere seguita da tutti gli sviluppatori e gli sviluppatori di ruby ​​che silenziosamente si aspettano che tu lo faccia. –

+0

Sarei cauto nel fare ciò poiché è il tipo di cosa che può causare confusione, irrompere nelle versioni future e, possibilmente, causare problemi per alcune gemme di terze parti. a meno che non sia ufficialmente consigliato. Sono tutto per gli hack generali/soluzioni alternative, ma il costruttore è una caratteristica particolarmente fragile e 'after_initialize' offre un'alternativa semplice. – mahemoff

23

Sicuramente after_initialize :add_product, if: :new_record? è il modo più pulito qui.

Mantenere il condizionale dalla funzione add_product

+1

Questa è probabilmente la strada giusta da percorrere. – fatuhoku

+0

fa 'after_initialize: add_product, su:: create' work? –

Problemi correlati