【问题标题】:Possible to eager load associations with nested_attributes?可以急切地加载与nested_attributes 的关联吗?
【发布时间】:2010-09-29 05:02:57
【问题描述】:

简单地说,我遇到了一个可怕的 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 生成嵌套表单,您可以如何预先加载关联。

感谢所有回复 :) 〜罗比

【问题讨论】:

    标签: ruby-on-rails nested-forms eager-loading nested-attributes


    【解决方案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
    

    【讨论】:

    • 这不太行,因为我仍然需要遍历所有可能的技能(参见 stub_player_skills)并检查 PlayerSkill 是否已经存在。假设我们有技能#1、#2 和#3。如果我有技能#1 和#3,那么我们需要排除#2。我认为问题是我正在使用 ActiveRecord 进行另一个“查找”,我应该只在我的业务逻辑中迭代数组吗?
    • 对不起,我完全错过了那部分。我可以看到你现在想要做什么......我会修改我的帖子。
    • 稍作修改后,我已经可以正常工作,控制器不再需要 n 查询来加载 PlayerSkills。但是,该视图仍然需要 n 次查询,因为我将其称为 ps.object.skill.name;在我看来,这可能是 Rails 处理 fields_for 的一种限制,这是相当不幸的。
    • 您是否尝试将 :include 添加到您的模型中?例如has_many :player_skills, :dependent => :destroy, :include => :skill
    • 是的,问题是每次从“child.build”调用“父”时它都会命中数据库,因为它们不能被急切地加载(因为它们不存在于数据库中)我'通过在 PlayerSkill 上使用另一个属性“名称”,我们已经解决了这个问题,并否定了 n 次查询,但实际上并没有得到保存。相关贴:pastie.org/1185550(然后view就吐出来了@skills_arr,正好排序正确)
    【解决方案2】:

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

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

      %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
    

    在模型层,我只需要在 has_many :through 关系上:include =&gt; :skill 即可摆脱更多查询。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-24
      • 1970-01-01
      • 1970-01-01
      • 2011-03-16
      相关资源
      最近更新 更多