I’m documenting a simple, fairly elegant method of handling simple has_and_belongs_to_many (habtm) relationships, for example assigning many tags to an individual article.
Note: Since Rails 0.13, this is supported in a more elegant way via automatically generated methods in the model as described here: CheckboxHABTM
This covers a helper method and its use in the view, and implementation in the controller, for the complete package deal.
Caveat: This is not the best code ever (far from it), I know, but it does work and it’s the best solution I’ve seen at this point. I figure that it’s better to put it out there as an example for folks than to wait til I make it better (e.g. just this side of never) and keep it to myself.
This application helper will create a palatable checkbox group for your tags, authors, or whatever you will be creating a simple habtm relationship for—and when you view an existing article, book, or whatever, will automatically check the checkboxes that represent existing relationships.
This goes in your application helper (or in a particular controller’s helper):
# would like to abstract this further. at some later date, maybe... def build_checklist_group(collection) str="" for tag in collection str << %{<input type="checkbox" name="tags[]" id="tag_#{tag.id}" value="#{tag.id}" } str << " checked='checked'" if @article.tags.include?(tag) str << %{/> #{tag.name} <br />} end str end
Edit the names as necessary—although, if you’re working with tags, this will work nicely for you.
You will of course be calling the helper from the relevant view, but first you must fetch the collection to feed it, in the controller method/action you are using that will generate the form that features the checkboxes :
@tags = Tag.find_all
Then, in the view itself:
<b>Tags:</b> <%= build_checklist_group(@tags) %>
On to the controller.
Now, to save a new item with these tags, this is all that is necessary:
# selected tags @article.tags << Tag.find(@params['tags'])
To edit an existing item with, presumably, existing tag relationships, it is 2 lines:
# destroy removed tags @article.tags.delete(@article.tags - Tag.find(@params["tags"])) # add new ones @article.tags << Tag.find(@params['tags']) - @article.tags
The first line removes any relationships with tags that you unchecked. The second line ensures no duplicate relationships will be made with the checkboxes you left checked. I have little doubt that this could be refactored further, but I actually like it like this.
A start-to-finish tutorial on this subject exists here – jrh