2

简而言之,我遇到了一个可怕的 2(n) 查询问题。如果 n = 数据库中的技能数量,那么我的 characters#edit 表单将需要 2(n) 次查询来加载页面。它会在每个技能中选择一个 PlayerSkill(连接表),并且每个技能都会查找一次技能。

这是一些我认为与情况相关的代码。本质上,这个过程涉及的模型、视图和控制器,少了模型验证,少了我不关心的动作。

控制器:

  # 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

型号:

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 生成嵌套表单,您可以如何急切地加载关联。

感谢您的所有回复:) ~Robbie

4

2 回答 2

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
于 2010-09-27T23:30:11.720 回答
0

如果有人对我对这个问题的“最终”解决方案感兴趣:

我已经存储了一个技能名称数组,并通过计数器在视图中引用它,如下所示:

  %p
    - index = 0
    -f.fields_for :player_skills do |ps|
      =@skill_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 :through 关系上摆脱更多的查询。

于 2010-09-28T03:14:11.287 回答