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

时间:2010-06-14 05:12:15

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

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

  • 字符
  • 技能
  • PlayerSkills

现在,PlayerSkills有一个Skills通常不具备的属性:一个级别。

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

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

所以在模特中没什么太奇特的...... 此时控制器也非常基本......这几乎是股票更新行动。

我想要修改的表单是字符#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:通过很棒,因为它允许你将关系本身视为一个实体......但我完全不知道如何处理这种形式的实体。 / p>

一如既往,感谢您提前给予我的任何帮助!

2 个答案:

答案 0 :(得分: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 = [])的原因是因为否则它会不正确地设置顺序。

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

我觉得这不是最好的解决方案 - 实际上对我来说感觉相当骇人。 因此,如果有其他人想要更好,更快/更优雅的解决方案,请随意!

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

答案 1 :(得分:0)

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

对于控制器:

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

希望它会有所帮助!

相关问题