Reputation: 725
I've managed to set up a many-to-many relationship between the following models
PlayerSkills, right now, has an attribute that Skills don't normally have: a level.
The models look something like this (edited for conciseness):
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
So nothing too fancy in the models... The controller is also very basic at this point... it's pretty much a stock update action.
The form I'm looking to modify is characters#edit. Right now it renders a series of checkboxes which add/remove skills from the characters. This is great, but the whole point of using has_many :through was to track a "level" as well.
Here is what I have so far:
- 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
After it renders "skill.name", I need it to print a text_field that updates player_skill.
The problem, of course, is that player_skill may or may not exist! (Depending on if the box was already ticked when you loaded the form!)
From everything I've read, has_many :through is great because it allows you to treat the relationship itself as an entity... but I'm completely at a loss as to how to handle the entity in this form.
As always, thanks in advance for any and all help you can give me!
Upvotes: 2
Views: 2283
Reputation: 725
I, so far, have fixed the problem I was having...
It was actually relatively straight forward once I learned about nested attributes!
Here is the new characters model!
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
And the update action for the character controller was mildly changed:
@character.skills_pre_update(params)
params[:character].delete(:player_skills_attributes)
params[:character].delete(:skill_ids)
The reason being, those two portions are already handled by the pre_update action, therefore they don't need to be handled again by update_attributes, which gets called later.
The view was relatively straight forward. the Many-to-Many checkboxes are still the same, I did however add the new textboxes!
- @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)
In essence, the reason I have to blank out skill_ids (skill_ids = []
) in the Characters model is because otherwise it improperly sets the order.
In essence, I add all the skills.
Update the levels, using the text-boxes.
Then reset the skills to what the user actually checked (which will delete any unused skills.)
I don't feel this is the greatest solution - in fact it feels rather hackish to me. So if anybody else wants to chime in with a better, possibly faster/more elegant solution, feel free!
Otherwise, I do hope this helps someone else... because modifying the extra attributes on the join table (without giving the join table its own controller/views) was a real pain!
Upvotes: 1
Reputation: 3671
I am not sure about the answer but here is what I think :
For the controller :
@character = Character.find(params[:id])
In the view :
<% if @character.skills!=0 %>
<% for skill in @character.skills %>
<%=h skill.name %>
<%= check_box_tag(skill.name, value = "1", checked = false, options = {...}) %>
<% end %>
<% end %>
Hope it will help!
Upvotes: 0