web-dev-qa-db-fra.com

Rails - Meilleure pratique: comment créer des relations has_one dépendantes

Pourriez-vous me dire quelle est la meilleure pratique pour créer des relations has_one?

f.e. si j'ai un modèle utilisateur et qu'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 semble pas très propre ... Tout suggère?

74
BvuRVKyUVlViVIc7

La meilleure pratique pour créer une relation has_one consiste à utiliser le rappel ActiveRecord before_create plutôt que after_create. Ou utilisez un rappel encore plus tôt et résolvez les problèmes (le cas échéant) de l'enfant qui ne passe pas sa propre étape de validation.

Car:

  • avec un bon codage, vous avez la possibilité de montrer les validations de l'enregistrement enfant à l'utilisateur si les validations échouent
  • il est plus propre et explicitement pris en charge par ActiveRecord - AR remplit automatiquement la clé étrangère dans l'enregistrement enfant après avoir sauvegardé 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
122
Larry K

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

# user.rb
class User < ActiveRecord::Base
  has_one      :profile
  after_create :create_profile
end
28
Bo Jeanes

S'il s'agit d'une nouvelle association dans une grande base de données existante, je gérerai 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 obtiennent un profil lorsqu'on le lui demande et que de nouveaux soient créés avec. Cela place également les attributs par défaut à un seul endroit et fonctionne correctement avec accept_nested_attributes_for dans Rails 4 et suivants).

22
inopinatus

Ce n'est probablement pas la solution la plus propre, mais nous avions déjà une base de données avec un demi-million d'enregistrements, dont certains avaient déjà créé le modèle "Profil", et d'autres non. Nous avons opté pour cette approche, qui garantit qu'un modèle Profile est présent à tout moment, sans avoir à parcourir et générer rétroactivement tous les modèles Profile.

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

Voici comment je le fais. Je ne sais pas à quel point c'est standard, mais cela fonctionne très bien et c'est paresseux en ce qu'il ne crée pas de surcharge supplémentaire à moins qu'il ne soit nécessaire de créer la nouvelle association (je suis heureux d'être corrigé à 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 y avoir des moments où vous ne vous souciez pas d'accéder à l'association. Il semble être un gaspillage de vérifier son existence.

5
Brendon Muir

Il y a un petit bijou pour ça:

https://github.com/jqr/has_one_autocreate

On dirait que c'est un peu vieux maintenant. (ne fonctionne pas avec Rails3)

1
linjunhalida