61

Pourriez-vous me dire quelle est la meilleure pratique pour créer des relations has_one?Rails - Best-Practice: Comment créer des relations dépendantes has_one

f.e. si j'ai un modèle d'utilisateur, et il doit avoir un profil ...

Comment pourrais-je accomplir cela?

Une solution serait:

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

    def set_default_association 
    self.create_profile 
    end 
end 

Mais cela ne marche pas sembler très propre ... Tout suggère?

Répondre

107

Les meilleures pratiques pour créer relation has_one est d'utiliser la fonction de rappel before_create ActiveRecord plutôt que after_create. Ou utilisez un rappel encore plus précoce et faites face aux problèmes (le cas échéant) de l'enfant ne passant pas sa propre étape de validation.

Parce que:

  • avec un bon codage, vous avez la possibilité pour les validations de l'enregistrement des enfants à afficher à l'utilisateur si les tests échouent
  • c'est plus propre et soutenu explicitement par ActiveRecord - AR remplissages automagiquement dans la clé étrangère de l'enregistrement enfant après avoir enregistré l'enregistrement parent (lors de la création). AR enregistre ensuite l'enregistrement enfant dans le cadre de la création de l'enregistrement parent.

Comment faire:

# 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 pour connaître la validation des enfants. – PeterWong

+0

Est-ce que cela pourrait aussi être géré avec une seule ligne? -> before_filter: build_profile? – Lichtamberg

+1

@Lichtamberg: Oui, mais j'ajouterais un commentaire: "Construit le profil par défaut, DOIT toujours valider." NOTE: ce serait "before_create: build_profile" et non "before_filter". Si elle ne valait pas, vous obtiendriez une erreur très confuse msg à l'utilisateur. Ou cela ne serait PAS créé, ce qui signifierait que vous finiriez avec un utilisateur sans profil. Vous devriez également tester les cas de coin dans vos tests. –

24

Votre solution est certainement une façon décente de le faire (au moins jusqu'à ce que vous dépassez la taille), mais vous pouvez le simplifier:

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

probablement pas la solution la plus propre, mais nous avions déjà une base de données avec un demi-million de dossiers, qui avaient déjà une partie du modèle « Profil » créé, et certains d'entre eux ne l'ont pas fait. Nous sommes allés avec cette approche, qui garantit un modèle de profil est présent à tout moment, sans avoir besoin de passer par et de générer rétroactivement tous les modèles de profil.

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

Voilà comment je le fais. Je ne sais pas comment cette norme est, mais il fonctionne très bien et son paresseux en ce qu'elle ne crée pas une charge supplémentaire à moins qu'il est nécessaire de construire la nouvelle association (Je suis heureux d'être corrigée à ce sujet):

def profile_with_auto_build 
    build_profile unless profile_without_auto_build 
    profile_without_auto_build 
end 

alias_method_chain :profile, :auto_build 

Cela signifie également que l'association est là dès que vous en avez besoin. Je suppose que l'alternative est de se connecter à after_initialize, mais cela semble ajouter un peu de surcharge car il est exécuté chaque fois qu'un objet est initialisé et il peut arriver que vous ne souhaitiez pas accéder à l'association. Cela semble être un gâchis à vérifier pour son existence.

+0

Je pense que cette solution est meilleure que d'autres, car elle évite les problèmes de validation dans le modèle Profile. Merci – ole

+0

vous pouvez également avoir autosave: 'has_one: profile,: autosave => true' – montrealmike

+0

@montrealmike, est-ce que ça a affaire à un profil manquant pour commencer? C'est à dire. si on n'a pas déjà exécuté build_profile, est-ce que cela en créerait un sur save? J'ai également rencontré ceci: https://github.com/phildionne/associates qui pourrait fournir un autre moyen de contourner les formulaires multi-modèles. –

19

Si cela est une nouvelle association dans une vaste base de données existante, je vais gérer la transition comme ceci:

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 

afin que les enregistrements d'utilisateurs existants acquérir un profil lorsqu'on lui a demandé ce et de nouveaux sont créés avec. Cela place également les attributs par défaut à un endroit et fonctionne correctement avec accept_nested_attributes_for dans Rails 4 et versions ultérieures.

Questions connexes