代码之家  ›  专栏  ›  技术社区  ›  Robbie

是否可以预先加载与嵌套属性的关联?

  •  2
  • Robbie  · 技术社区  · 14 年前

    简单地说,我遇到了一个可怕的2(n)个查询问题。 如果n=数据库中的技能数量,那么我的字符编辑表单将接受2(n)个查询来加载页面。它将选择一个玩家杀死(加入表)每一个技能一次,它将查找每一个技能一次的技能。

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

    控制器:

      # 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查询”的来源。

    然后在视图中,字段“for”循环遍历我们收集的所有玩家技能,因为在这里不可能急于加载,当我调用=ps.object.skill.name打印出技能的名称时,它执行一个技能查找,这将引入第二组“n-queries”。

    我主要关心的是视图层,我找不到任何文档(rails api或其他文档),说明如果您使用fields\for生成嵌套表单,那么您如何能够急于加载关联。

    感谢您的所有回复:) ~罗比

    2 回复  |  直到 14 年前
        1
  •  1
  •   Coderama    14 年前

    你能试试这个看看它能不能用?

    你可以保持你的模型不变。

    然后你的控制器会像这样

    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
    
        2
  •  0
  •   Robbie    14 年前

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

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

      %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
    

    在控制器中,我已经将几乎所有的逻辑转移到了存根播放器技能方法所属的地方,并从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 关于有很多:通过关系来摆脱更多的查询。