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

使用Rails更新has_many,:through关系中的额外属性

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

    我已经成功地建立了以下模型之间的多对多关系

    • 技能

    现在,玩家技能有一个技能通常不具备的属性:等级。

    模型如下所示(为简洁起见进行了编辑):

    class PlayerSkill < ActiveRecord::Base
      belongs_to :character
      belongs_to :skill
    end
    
    class Skill < ActiveRecord::Base
      has_many :player_skills
      has_many :characters, :through => :player_skills
    
      attr_accessible :name, :description
    end
    
    class Character < ActiveRecord::Base
      belongs_to :user
    
      has_many :player_skills
      has_many :skills, :through => :player_skills
    end
    

    所以模特们没什么特别的。。。 控制器在这一点上也是非常基本的。。。这几乎是一个股票更新行动。

    我要修改的表单是characters#edit。 现在它呈现一系列复选框,这些复选框添加/删除角色的技能。

    到目前为止,我掌握的情况如下:

    - 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
        - @skills.each do |skill|
          = check_box_tag "character[skill_ids][]", skill.id, @character.skills.include?(skill)
          =h skill.name
          %br
      %p
        = f.submit
    

    当然,问题是玩家的技能可能存在,也可能不存在!(取决于加载表单时是否已勾选该框!)

    从我读到的每一件事来看,有很多:通过是伟大的,因为它可以让你把关系本身作为一个实体。。。但我完全不知道如何处理这种形式的实体。

    一如往常,谢谢你能给我的一切帮助!

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

    一、 到目前为止,我已经解决了我遇到的问题。。。

    当我了解到嵌套属性时,它实际上是相对直接的!

    这是新的角色模型!

    class Character < ActiveRecord::Base
      belongs_to :user
    
      has_many :player_skills
      has_many :skills, :through => :player_skills
      accepts_nested_attributes_for :player_skills
    
      def skills_pre_update(params)
        skills = Skill.find(:all, :order => 'id')
        skills = skills.map do |skill|
          skill.id
        end
    
        self.skill_ids = []
        self.skill_ids = skills
    
        self.skill_ids.each_with_index do |skill_id, index|
          self.player_skills[index].level = params[:character][:player_skills_attributes][index][:level]
        end
    
        self.skill_ids = params[:character][:skill_ids]
      end
    end
    

    角色控制器的更新操作也发生了轻微的变化:

    @character.skills_pre_update(params)
    params[:character].delete(:player_skills_attributes)
    params[:character].delete(:skill_ids)
    

    原因是,这两个部分已经由pre\u update操作处理,因此不需要通过update\u属性(稍后调用)再次处理它们。

    观点相对直截了当。多对多的复选框仍然是一样的,但是我添加了新的文本框!

    - @skills.each_with_index do |skill,index|
      = check_box_tag "character[skill_ids][]", skill.id, @character.skills.include?(skill)
      =h skill.name
      -ps = skill.player_skills.find_by_character_id(@character) || skill.player_skills.build
      -fields_for "character[player_skills_attributes][]", ps do |psf|
        =psf.text_field(:level, :index => nil)
        =psf.hidden_field(:id, :index => nil)
    

    skill_ids = [] )在Characters模型中,是因为它不正确地设置了顺序。

    本质上,我添加了所有技能。
    使用文本框更新级别。
    然后将技能重置为用户实际检查的技能(这将删除所有未使用的技能)

    我不觉得这是最好的解决办法-事实上,我觉得这是相当黑客。 因此,如果其他人想提出更好、可能更快/更优雅的解决方案,请放心!

    否则,我真的希望这能帮助别人。。。因为修改联接表上的额外属性(不给联接表自己的控制器/视图)是一件非常痛苦的事情!

        2
  •  0
  •   Michaël BadZen    14 年前

    我不确定答案,但以下是我的想法:

    @character = Character.find(params[:id])
    

    <% if @character.skills!=0 %>
        <% for skill in @character.skills %>
            <%=h skill.name %>
            <%= check_box_tag(skill.name, value = "1", checked = false, options = {...}) %>
        <% end %>
    <% end %>