Можно ли загружать ассоциации с nested_attributes? - PullRequest
2 голосов
/ 28 сентября 2010

Вкратце, я столкнулся с ужасной проблемой 2 (n) запросов.Если n = количество навыков в базе данных, то моя форма редактирования символов # потребует 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

Модель(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

Мое понимание ситуации состоит в том, что существует стремящаяся загрузка, чтобы захватить ассоциацию (примерно) в одной дополнительнойquery.

Мне нужно правильно применить энергичную загрузку в двух областях, и я просто в растерянности относительно того, как это сделать.

В методе stub_player_skills необходимо создать объект PlayerSkill при условии, что у персонажа его еще нет. Может быть полезно загрузить его здесь, потому что он проходит через каждый навык в базе данных.Вот откуда приходят первые «n-запросы».

Затем в представлении fields_for проходит через все накопленные нами PlayerSkills, потому что здесь нет способа загружать, когда я вызываю= ps.object.skill.name, чтобы распечатать имя навыка, он выполняет поиск навыка, который вводит второй набор «n-запросов».

Моя основная задача заключается в слое представления, яне удается найти какую-либо документацию (Rails API или иным образом), в которой указано, как можно загружать ассоциации, если вы используете fields_for для создания вложенной формы.

Спасибо за все ответы :) ~ Robbie

Ответы [ 2 ]

1 голос
/ 28 сентября 2010

Можете ли вы попробовать это и посмотреть, будет ли это работать?

Вы можете сохранить свою модель такой, какая она есть.

Ваш контроллер может выглядеть следующим образом

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 голосов
/ 28 сентября 2010

На случай, если кто-нибудь заинтересуется моим «окончательным» решением этой проблемы:

Я прибег к хранению массива имен умений и обращаюсь к нему в виде через счетчик, как показано здесь:

  %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, которому он принадлежит, и, извлекая страницу из книги Кодерамы, я придумал следующее:

  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, чтобы избавиться от нескольких запросов.

...