#17
Apr 11
HABTM Checkboxes
It is often asked: how do I create a list of checkboxes for managing a HABTM association? Ask no more because this episode will show you how to do exactly that.
<!-- products/_form.rhtml --> <% for category in Category.find(:all) %> <div> <%= check_box_tag "product[category_ids][]", category.id, @product.categories.include?(category) %> <%= category.name %> </div> <% end %>
# products_controller.rb def update params[:product][:category_ids] ||= [] #... end




I used this screencast to create a checkbox list for a posts/categories relationship and found that in post/update where you have "params[:post][:category_ids] ||= []" that I had to add a "rescue nil" at the end up that line to make tests happy. Think I did the right thing?
@ben: Definitely. You're probably manipulating a page that _only_ has check boxes. Since a form of only check boxes (with none selected) won't submit anything for that param you have to handle the 'nil' exception.
It should also be noted that has_many relationships _do_ have a #relationship_ids= method, but has_many :through relationships don't.
A "gotcha" of this method of HABTM check box handling is that you lose some UI functionality. Because each of your check boxes will have NAME and VALUE like "model[habtm_relationship_ids][]", you can't pair them with a label in a manner that clicking on said label selects the checkbox.
Am I right in saying this isn't quite as straightforward when we try to implement with a has_many :through association?
I can get my checkboxes displaying (and checking) properly, but struggle to update - I think it's something to do with the ids being passed being those of the 'join' table.
Anyone cracked this, or am I going about it the wrong way?
@Ian,
Correct, it doesn't work the same way with has_many :through because this association doesn't have the "category_ids=" setter method. You need to create this on your own. See this thread for details:
http://railsforum.com/viewtopic.php?id=803
one thing im still not obvious to me is the
product[category_ids][] u wrote in the view.
does this mean product.category_ids => []
what does category_ids method do by itself only without the [].
Yeah, that's a little confusing. The "category_ids" method accepts an array of ids, so:
product.category_ids = [1, 2, 3]
Will set categories with ids 1, 2 and 3 to that product. Normally in Rails forms you put the name of the attribute in square brackets and use that as the name of the field:
product[category_ids]
This way Rails knows it's an attribute of product. But if you just did this, it would take only one check box value and set that:
product.category_ids = 1
But here the category_ids method expects an array, so you need those empty square brackets in the name of the check box. Rails will then gather up all values with this same name and place them in an array. This is what the empty square brackets do:
product[category_ids][]
I hope that made some sense.
Ah, yes. makes so much sense.
btw, how are you able to display the length running time for your .mov files? have you considered, displaying a snapshot of a random frame from a .mov file as a thumbnail? im just curious as how you are able to access the metadata of .mov files such as length, title, and etc.
@jjk2, no magic going on here. I'm just typing in that information manually. I'm sure there's some smart ways to have Ruby fetch the movie metadata, but the movies are on a separate server so that makes things considerably more difficult. If I do automate it, I will probably fetch the metadata on my local which has acccess to everything then have it automatically upload that info. So far I'm okay with typing it in manually though.
As for the thumbnail image, I'm not sure how useful it will be since half of the time it will display code which you can't read. I'll consider doing it if I get more requests though.
Thanks for the suggestions.
ah, i just realized it as i hit teh click...a thumbnail wouldn't do much for screencasts hahaha.
yea you would have to use rmovie, ffmpeg....to do what i described. it can be a quite a load on the server cpus.
keep up the good work!
Fantastic! I stumbled upon your railcast site whilst trying to work out how to handle HABTM checkboxes and bam, there's the solution right there! I found other stuff on the net but they were confusing or not quite what I wanted. Your railcast on this topic was exactly the solution I was looking for plus it was quick, easy and straight to the point. I'm very impressed. I'll be subscribing in iTunes and I'll be looking back over your previous railcasts now. Many thank!!
Hey Ryan, these screencasts are really nice, you're doing some really good work :-)
It seems that there's a bit of confusion about the differences between HABTM and has_many :through, particularly with these checkboxes. Maybe you might spend a bit of time explaining the differences, and/or running through the process of implementing these category checkboxes with a has_many :through relationship.
Thanks for the effort you're putting into this :-)
@Sam, the difference is subtle but very important. I'll try to dedicate a screencast to it in the future.
In short, normally you will have a more robust interface for a has_many :through association because the join model may likely have more fields than the two foreign keys. So you may need a checkbox and a text field for example. Usually this problem is solved with AJAX so it's not just simple checkboxes. I'll try to explain more in a future episode. Thanks for the suggestion
Hi Ryan ! Thanks for the great site. I loved this tutorial and found it helpful but couldn't make it work when I generate the scaffold using "scaffold resource" as for making it work RESTfuly (as accepting requests in various formats)
Hmm, it should work fine with a RESTful design because you're just editing a model's attributes like normal. Unless you have a has_many :through association instead of has_and_belongs_to_many, in that case you would have to create your own "*_ids" method.
Can you be more specific on what isn't working?
Okay, nothing else worked for me except this explanation. Even the Rails wiki was confusing. Thanks!!
Thanks a bunch for this. Very helpful and clear.
Great tutorial Ryan! This one really helped me out. Keep up the high quality work.
Im getting some problems...
Ive followed your tutorial step by step and the application tries to insert the 'id' field in categories_posts:
Mysql::Error: Duplicate entry '2' for key 1: INSERT INTO categories_posts (`post_id`, `id`, `category_id`) VALUES (19, 2, 2)
and it should be:
INSERT INTO categories_posts (`post_id`,`category_id`) VALUES (19,2)
Any help ?
Thanks
Ok, was my fault...
The database categories_posts shouldnt have an id field :)
@James:
what is wrong with doing
<label><%= check_box_tag "product[category_ids][]", category.id, @product.categories.include?(category) %> some label</label>
this pairs a label with the checkbox.
Why is it that all of the checkbox info on the net is for updating items that already exist. How can i do this for a new entry.
Here is a little fix for keeping the generated checkbox code standards complain :
<%= check_box_tag "product[category_ids][]",category.id, @product.categories.include?(category),{ :id => "product_category_ids_"+ category.id.to_s} %>
The example from Ryan puts "[]" into the id field of the input tag and this is not allowed according to the w3 ;)
Damn there's some spam in here... I think you need to implement a CAPTCHA.
Anyways - quick question - how can I use this with a self-referencing model?
For example, I have an Article model which I want to have Related Articles. Any pointers much appreciated.
Ryan,
you just saved me a bunch of time in having to figure this out on my own. Thank you!!
I'm using the technique you presented, but with a :has many :through. I've added the setter method to my model, and its editing fine. But when I add a new, I'm getting a "You cannot call create unless the parent is saved" error.
How do I get my parent object created before the children?
@Dan - I'm getting the same problem. I'll post back if I find a fix. Using a HMT really mixes things up!
Any idea how you'd manage validation for a check box like this, or more specifically, how check boxes could be highlighted if none are ticked?
If you put something like:
validates_presence_of :category_ids
in the products model ( to ensure that each product had at least one category box ticked) it's possible to display error messages when a form is submitted without any of the category boxes ticked, but how would you *highlight* the offending tickboxess?
I've just tried Alastair Moore's suggestion for binding labels to the HABTM checkboxes and it works perfectly. Thanks for a great Railscast, and thanks to Alastair.
I'm dealing with the same MySQL database error as Klian, above. My join table only has the two _id columns, though.
Thanks for the screencast! Extremely helpful. I spent an extra hour goofing around because I had used attr_accessible in my model, and I needed to add category_ids to the list of accessible attributes so that update_attributes would work. Hope my pain helps somebody else.
Ryan, how would you go about adapting this to select boxes rather than checkboxes? I have a habtm relationship between "stores" and "products", where each store can carry up to 10 'products', but I would like to display the 'products' in select rather than as checkboxes (as there are > 100 of them).
So, visualize looking at a 'store' page where you see 10 select boxes, all populated from the 'products' table.
I've been trying collection select and friends, but can't seem to get everything to play nicely together.
@Marcus - Thanx man, this info saved me quite a lot of time, thx!
@Ryan - keep on going!
How would one adapt this for creating new "products" and not just adding because it does not seem to work.
I have no idea why everyone else gets this to work. I followed it step by step by I couldn't. Are you supposed to create three tables: Categories, Products, and Categories_products? And I tried something like product:references when generating a scaffold and it produces product_id instead of product_ids...the railscast should provide more information on this.
holy donkey, finally get it to work but with the additional following:
1. delete the id field in categories_products table
2. add this: @expense.tags = Tag.find(params[:tag_ids]) if params[:tag_ids] to update action
I don't know why I need step 2 to make it work...I thought the update_attribute will do, but it didn't. Can someone tell me why?
In this episode you clearly showed how to handle checkboxes with HMABTM relations.
But you said in the comments that if you have many_to_many relations you have to create your own logic. Would you please expand on that in a future episode. I am an experienced software engineer up to now working in perl and php, but a newbee to Ruby and I greatly appreciate these podcasts. I have looked through the archive of episodes but don't see any response addressing my issue.
Carl