web-dev-qa-db-fra.com

Rails after_initialize uniquement sur "nouveau"

J'ai les 2 modèles suivants

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

Un sport ne peut pas exister sans le produit, donc dans mon sports_controller.rb J'ai eu:

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

J'ai essayé de déplacer la création du produit vers le modèle sport en utilisant after_initialize:

after_initialize :create_product

def create_product
 self.product = Product.new
end

J'ai rapidement appris que after_initialize est appelé chaque fois qu'un modèle est instancié (c'est-à-dire à partir d'un appel find). Ce n'était donc pas le comportement que je recherchais.

Quelle est la façon dont je devrais modéliser l'exigence que tous les sport aient un product?

Merci

49
Tyler DeWitt

Mettre la logique dans le contrôleur pourrait être la meilleure réponse comme vous l'avez dit, mais vous pouvez obtenir le after_initialize pour travailler en procédant comme suit:

after_initialize :add_product

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

De cette façon, il ne définit le produit que si aucun produit n'existe. Cela peut ne pas valoir la surcharge et/ou être moins clair que d'avoir la logique dans le contrôleur.

Edit: Selon la réponse de Ryan, en termes de performances, ce qui serait probablement mieux:

after_initialize :add_product

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

Sûrement after_initialize :add_product, if: :new_record? est le moyen le plus propre ici.

Gardez le conditionnel hors de la fonction add_product

40
Paul Odeon

Si tu fais self.product ||= Product.new il cherchera toujours un produit chaque fois que vous effectuez un find car il doit vérifier s'il est nul ou non. En conséquence, il n'effectuera aucun chargement ardent. Pour ce faire uniquement lorsqu'un nouvel enregistrement est créé, vous pouvez simplement vérifier s'il s'agit d'un nouvel enregistrement avant de configurer le produit.

after_initialize :add_product

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

J'ai fait quelques benchmarks et vérifications de base if self.new_record? ne semble pas affecter les performances de manière notable.

27
Ryan

À la place d'utiliser after_initialize, que diriez-vous after_create?

after_create :create_product

def create_product
  self.product = Product.new
  save
end

Est-ce que cela résoudrait votre problème?

2
James Brooks

On dirait que tu es très proche. Vous devriez pouvoir supprimer complètement l'appel after_initialize, mais je pense d'abord que si votre modèle Sport a une relation "has_one" avec: produit comme vous l'avez indiqué, votre modèle de produit devrait également "appartenir" au sport. Ajoutez ceci à votre modèle de produit

belongs_to: :sport

L'étape suivante, vous devriez maintenant pouvoir instancier un modèle Sport comme ça

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

Ceci est basé sur les informations de Association Basics from Ruby on Rails Guides, que vous pourriez lire si je le suis) pas exactement correct

1
coderates

Vous devez simplement remplacer la méthode d'initialisation comme

class Sport < ActiveRecord::Base

  # ...

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

  # ...

end

La méthode d'initialisation n'est jamais appelée lorsque l'enregistrement est chargé à partir de la base de données. Notez que dans le code ci-dessus, les attributs sont attribués après la création du produit. Dans un tel paramètre, l'attribution des attributs peut affecter l'instance de produit créée.

0
Victor Nazarov