2

Juste brièvement, j'ai rencontré un problème redouté 2 (n) requêtes. Si n = le nombre de compétences dans la base de données, mon formulaire d'édition de caractères # prendra 2 (n) requêtes pour charger la page. Il va sélectionner une compétence PlayerSkill (la table de jointure) une fois par compétence, et il recherchera la compétence une fois par compétence.Possibilité de créer des associations de chargement avec nested_attributes?

Voici un code que je crois pertinent à la situation. En substance, les modèles, vues et contrôleurs impliqués dans ce processus, moins les validations du modèle et moins les actions qui ne me concernent pas.

Le contrôleur:

# GET /characters/1/edit 
    def edit 
    @character = Character.find(params[:id], :include => {:player_skills => :skill}) 
    stub_player_skills 
    end 

    private 
    def stub_player_skills 
     @skills = Skill.find(:all) 
     @skills.each do |skill| 
     if (skill.player_skills.empty?) 
      ps = @character.player_skills.build(:skill_id => skill.id, :name => skill.name) 
     end 
     end 
    end 

Le modèle (s):

class Character < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :campaign 
    has_many :sheets, :dependent => :destroy 
    has_many :tokens, :dependent => :destroy 

    has_many :player_skills, :dependent => :destroy 
    has_many :skills, :through => :player_skills 
    accepts_nested_attributes_for :player_skills, :allow_destroy => true 
end 

La vue incriminée (HAML):

%h1 
    Editing Character 

- form_for @character do |f| 
    = f.error_messages 
    %p 
    = f.label :name 
    %br 
    = f.text_field :name 
    %p 
    = f.label :race 
    %br 
    = f.text_field :race 
    %p 
    = f.label :char_class 
    %br 
    = f.text_field :char_class 
    %p 
    -f.fields_for :player_skills do |ps| 
     =ps.object.skill.name 
     =ps.text_field :level 
     =ps.hidden_field :skill_id 
     -unless ps.object.new_record? 
     =ps.check_box '_destroy' 
     =ps.label '_destroy', 'Remove' 
     %br 
    %p 
    = f.submit 

Ma compréhension de la situation est que le chargement désireux existe pour saisir l'association dans (grossièrement) une seule requête supplémentaire.

J'ai besoin d'appliquer correctement le chargement dans deux domaines, et je suis juste à une perte quant à la façon de le faire.

Dans la méthode stub_player_skills, il doit créer un objet PlayerSkill en supposant que le caractère n'en possède pas déjà un. Il pourrait bénéficier d'un chargement passionné ici, car il boucle à travers chaque compétence dans la base de données. C'est de là que viennent les premières "n-requêtes". Ensuite, dans la vue, fields_for parcourt toutes les PlayerSkills que nous avons accumulées, car il n'y a aucun moyen de charger avec impatience ici, quand j'appelle = ps.object.skill.name pour imprimer le nom de la compétence. fait une recherche de compétence, qui apporte le deuxième ensemble de "n-requêtes". Ma principale préoccupation réside dans la couche de vue, je ne trouve aucune documentation (Rails API ou autre) qui indique comment vous pouvez charger les associations avec impatience si vous utilisez fields_for pour générer un formulaire imbriqué.

Merci pour tout et toutes les réponses :) ~ Robbie

Répondre

1

Pouvez-vous essayer cela et voir si cela va fonctionner?

Vous pouvez conserver votre modèle tel qu'il est.

Votre contrôleur peut alors ressembler à ceci

def edit 
    # Get all the skill objects once only 
    skills = Skill.find(:all) 

    # Used to extract Skill#name 
    skills_hash = {} 
    skills.map { |s| skills_hash[s.id] = s.name } 

    # Create an array of the skill-ids 
    skill_ids = skills.map { |s| s.id } 

    @character = Character.find(params[:id]) 

    # Determine the character's missing skills 
    skill_ids -= @character.player_skill_ids 

    # Build all of the missing skills 
    skill_ids.each do |id| 
    @character.player_skills.build(:skill_id => id, :name => skills_hash[id]) 
    end 
end 
+0

Cela ne fonctionne pas parce que je dois encore parcourir toutes les compétences possibles (voir stub_player_skills) et vérifier si un PlayerSkill existe déjà. Imaginez que nous ayons les compétences 1, 2 et 3. Si j'ai les compétences n ° 1 et n ° 3, nous devons remplacer le n ° 2. Je pense que le problème est que j'utilise ActiveRecord pour faire une autre "recherche", est-ce que je devrais simplement parcourir le tableau dans ma logique métier à la place? – Robbie

+0

Je suis désolé, j'ai totalement raté cette partie. Je peux voir ce que vous essayez de faire maintenant ... Je vais réviser mon message. – Coderama

+0

Avec une légère modification, cela a fonctionné et le contrôleur n'a plus besoin de requêtes n pour charger les PlayerSkills. La vue cependant prend toujours des requêtes n parce que je l'appelle ps.object.skill.name; Il me semble que cela peut être une limitation dans la manière dont Rails gère fields_for, ce qui est plutôt regrettable. – Robbie

0

En cas anyones intéressés par ma solution « finale » à ce problème:

J'ai eu recours à stocker un tableau des noms de compétences et le référencement dans la vue par un compteur, comme on le voit ici:

%p 
    - index = 0 
    -f.fields_for :player_skills do |ps| 
     [email protected]_arr[index] 
     =ps.text_field :level 
     =ps.hidden_field :skill_id 
     -unless ps.object.new_record? 
     =ps.check_box '_destroy' 
     =ps.label '_destroy', 'Remove' 
     - index += 1 
     %br 

dans le contrôleur, je me suis déplacé presque tous les t il logique à la méthode stub_player_skills où il appartient, et de prendre une page du livre de Coderama, je suis venu à ceci:

private 
    def stub_player_skills 
     @skills = Skill.find(:all) 
     @skills.each do |skill| 
     skill_exists = @character.player_skills.select do |i| 
      i.skill_id == skill.id 
     end 
     if skill_exists.empty? 
      ps = @character.player_skills.build(:skill_id => skill.id, :name => skill.name) 
     end 
     end 

     @skill_arr = @character.player_skills.map do |el| 
     el.name.nil? ? el.skill.name : el.name 
     end 
    end 

Dans la couche de modèle, je devais juste :include => :skill sur le has_many: par rapport se débarrasser de quelques requêtes supplémentaires.

Questions connexes