2

簡而言之,我遇到了一個可怕的2(n)查詢問題。 如果n =數據庫中的技能數量,那麼我的角色#編輯表單將花費2(n)個查詢來加載頁面。它會在每個技能中選擇一次PlayerSkill(連接表),並且每個技能都會查找一次技能。可能與nested_attributes緊密加載關聯嗎?

下面是一些我認爲與情況相關的代碼。實質上,這個過程中涉及的模型,視圖和控制器,不是模型驗證,而是我不關心的操作。

控制器:

# 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 

模型(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 

違規的視圖(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 

我瞭解的情況是,預先加載存在以抓取關聯(大致)一個額外的查詢。

我需要正確地應用在兩個領域的急切加載,我只是在如何做到這一點上的損失。

在stub_player_skills方法中,它需要創建一個PlayerSkill對象,假定該字符還沒有。 它可以受益於這裏的熱切加載,因爲它循環了數據庫中的每項技能。這是第一個「n查詢」來自哪裏。

然後在視圖上,fields_for循環遍歷所有我們累積起來的PlayerSkills,因爲當我調用= ps.object.skill.name打印出技能名稱時,無法在這裏急切加載,做了一次技能查詢,它引入了第二組「n查詢」。

我主要關注的是視圖層,我找不到任何文檔(Rails API或其他),說明如何使用fields_for生成嵌套表單,以便如何加載關聯。

感謝您的任何和所有響應:) 〜羅比

回答

1

你可以試試這個,看看它是否會工作?

你可以保持你的模型的方式。

那麼你的控制器能夠像這樣

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

這並不完美,因爲我仍然需要遍歷所有可能的技能(請參閱stub_player_skills)並檢查PlayerSkill是否已經存在。 想象一下,我們有技能#1,#2和#3。如果我有技能#1和#3,那麼我們需要將#2排除。 我認爲問題是我使用ActiveRecord來做另一個「查找」,我應該只是在我的業務邏輯中迭代數組? – Robbie 2010-09-27 23:42:47

+0

對不起,我完全錯過了那部分。我可以看到你現在想做的事情......我會修改我的帖子。 – Coderama 2010-09-27 23:50:27

+0

稍微修改我已經得到了這個工作,並且控制器不再需要n個查詢來加載PlayerSkills。然而,該視圖仍然需要n個查詢,因爲我讓它調用ps.object.skill.name;在我看來,這可能是Rails如何處理fields_for的一個限制,這很不幸。 – Robbie 2010-09-28 00:12:02

0

在有興趣在我的「最後」解決這個問題的情況下,任何人:

我使出存儲技術名稱的數組,並通過反向引用它在視圖中,如下所示:

%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 

在控制器中,我幾乎所有的移動ŧ他的邏輯到它所屬的stub_player_skills方法,並採取了首頁輸出Coderama的書,我想出了這一點:

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 

在模型層,我不得不:include => :skill上的has_many:通過關係擺脫幾個更多的疑問。