61

Potrebbe dirmi qual è la procedura migliore per creare has_one relations?Rails: best-practice: come creare relazioni has_one dipendenti

f.e. se ho un modello utente, e deve avere un profilo ...

Come potrei farlo?

Una soluzione potrebbe essere:

# user.rb 
class User << ActiveRecord::Base 
    after_create :set_default_association 

    def set_default_association 
    self.create_profile 
    end 
end 

Ma quello non sembrano molto pulito ... Qualsiasi suggerisce?

risposta

107

Le migliori pratiche per creare relazione has_one è quello di utilizzare il callback ActiveRecord before_create piuttosto di after_create. Oppure utilizzare una callback ancora precedente e affrontare i problemi (se ce ne sono) del bambino che non superano il proprio passo di convalida.

Perché:

  • con buona codifica, avete la possibilità per le convalide del record bambino per dimostrare che l'utente se le convalide non riescono
  • è più pulito ed esplicitamente supportato da ActiveRecord - AR riempie automagically nella chiave esterna nel record figlio dopo aver salvato il record padre (su create). AR salva quindi il record figlio come parte della creazione del record padre.

Come fare:

# in your User model... 
has_one :profile 
before_create :build_default_profile 

private 
def build_default_profile 
    # build default profile instance. Will use default params. 
    # The foreign key to the owning User model is set automatically 
    build_profile 
    true # Always return true in callbacks as the normal 'continue' state 
     # Assumes that the default_profile can **always** be created. 
     # or 
     # Check the validation of the profile. If it is not valid, then 
     # return false from the callback. Best to use a before_validation 
     # if doing this. View code should check the errors of the child. 
     # Or add the child's errors to the User model's error array of the :base 
     # error item 
end 
+3

+1 per la conoscenza della convalida del bambino. – PeterWong

+0

Potrebbe anche essere gestito con una singola linea? -> before_filter: build_profile? – Lichtamberg

+1

@Lichtamberg: Sì, ma aggiungerei un commento: "Crea un profilo predefinito, DEVE sempre convalidare". NOTA: sarebbe "before_create: build_profile" non "before_filter". Se non si convalida, si otterrebbe un messaggio di errore molto confuso all'utente. O NON verrebbe infatti creato, il che significherebbe che si finirebbe con un utente senza un profilo. Dovresti anche testare i casi d'angolo nei tuoi test. –

24

tua soluzione è sicuramente un modo decente per farlo (almeno fino a quando non troppo grande), ma è possibile semplificare esso:

# user.rb 
class User < ActiveRecord::Base 
    has_one  :profile 
    after_create :create_profile 
end 
5
non

Probabilmente la soluzione più pulita, ma abbiamo già avuto un database con mezzo milione di dischi, alcuni dei quali avevano già il modello 'Profilo' creato, e alcuni dei quali no. Siamo partiti con questo approccio, che garantisce che un modello Profile sia presente in qualsiasi momento, senza la necessità di passare e generare retroattivamente tutti i modelli Profile.

alias_method :db_profile, :profile 
def profile 
    self.profile = Profile.create(:user => self) if self.db_profile.nil? 
    self.db_profile 
end 
4

Ecco come lo faccio. Non sono sicuro di come standard di questo è, ma funziona molto bene e la sua pigra nel senso che non crea overhead aggiuntivo meno che non sia necessario costruire la nuova associazione (io sono felice di essere corretto su questo):

def profile_with_auto_build 
    build_profile unless profile_without_auto_build 
    profile_without_auto_build 
end 

alias_method_chain :profile, :auto_build 

Questo significa anche che l'associazione è presente non appena ne hai bisogno. Immagino che l'alternativa sia quella di collegarsi a after_initialize, ma questo sembra aggiungere un po 'di overhead dato che viene eseguito ogni volta che un oggetto viene inizializzato e ci possono essere momenti in cui non ti interessa accedere all'associazione. Sembra uno spreco per verificare la sua esistenza.

+0

Penso che questa risposta sia una soluzione migliore di altre, perché evita problemi con la convalida nel modello Profile. Grazie a – ole

+0

puoi anche avere il salvataggio automatico: 'has_one: profile,: autosave => true' – montrealmike

+0

@montrealmike, si tratta di un profilo mancante per cominciare? Cioè se uno non ha già eseguito build_profile, creerebbe uno su save? Mi sono imbattuto anche in questo: https://github.com/phildionne/associates che potrebbe fornire un altro modo per creare moduli multi-modello. –

19

Se questa è una nuova associazione in un database di grandi dimensioni esistente, io gestire la transizione in questo modo:

class User << ActiveRecord::Base 
    has_one :profile 
    before_create :build_associations 

    def profile 
    super || build_profile(avatar: "anon.jpg") 
    end 

private 
    def build_associations 
    profile || true 
    end 
end 

in modo che i record utente esistenti ottenere un profilo quando l'è cercata e ne vengono creati di nuovi. Questo pone anche gli attributi di default in un posto e funziona correttamente con accept_nested_attributes_for in Rails 4 in poi.