【问题标题】:Updating extra attributes in a has_many, :through relationship using Rails使用 Rails 更新 has_many, :through 关系中的额外属性
【发布时间】:2010-06-14 05:12:15
【问题描述】:

我已经设法在以下模型之间建立了多对多关系

  • 字符
  • 技能
  • 玩家技能

PlayerSkills,现在,有一个技能通常没有的属性:一个级别。

模型看起来像这样(为简洁而编辑):

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。 现在它呈现了一系列复选框,用于添加/删除角色的技能。 这很棒,但使用 has_many :through 的全部目的也是为了跟踪“级别”。

这是我目前所拥有的:

- 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

在它呈现“skill.name”之后,我需要它打印一个更新 player_skill 的 text_field。

当然,问题在于 player_skill 可能存在也可能不存在! (取决于您在加载表单时是否已经勾选了该框!)

从我读过的所有内容来看,has_many :through 很棒,因为它允许您将关系本身视为一个实体......但我完全不知道如何处理这种形式的实体。

与往常一样,提前感谢您给予我的任何帮助!

【问题讨论】:

    标签: ruby-on-rails activerecord many-to-many


    【解决方案1】:

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

    一旦我了解了嵌套属性,这实际上是相对简单的!

    这是新的角色模型!

    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_update 操作处理,因此它们不需要由稍后调用的 update_attributes 再次处理。

    观点相对简单。多对多复选框仍然相同,但我确实添加了新的文本框!

    - @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)
    

    本质上,我必须在 Characters 模型中删除 Skill_ids (skill_ids = []) 的原因是因为它不正确地设置了顺序。

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

    我不认为这是最好的解决方案 - 事实上,我觉得这很 hackish。 因此,如果其他人想要提出更好、可能更快/更优雅的解决方案,请随意!

    否则,我确实希望这对其他人有所帮助...因为修改连接表上的额外属性(不给连接表提供自己的控制器/视图)真的很痛苦!

    【讨论】:

    • 我想指出,您必须为连接模型上所需的每个额外属性修改 pre_update 方法。这显然不理想。
    • 您可以在集合上使用fields_for。它节省了大量的实现细节。
    【解决方案2】:

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

    对于控制器:

    @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 %>
    

    希望对您有所帮助!

    【讨论】:

      猜你喜欢
      • 2013-01-05
      • 1970-01-01
      • 2010-09-20
      • 2012-07-21
      • 2011-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-06
      相关资源
      最近更新 更多