has_many :through and SimplyHelpful form_for 1

Posted by Tim Connor Thu, 15 Mar 2007 19:49:00 GMT

So given how much SimplyHelpful could simplify things by allowing me to have one shared form for both new and edit, all magically handled with a simple "form_for @my_model do |f| ",of course, I jumped at it.

Equally as “of course,” the most complicated piece of the Lost River site that I have been working on is not a simple, single model form, but some variation of a many to many relationship. In fact, it was something best modeled by a “has_many :through” and a case where I definitely wanted the join models easily editable from the creation form.

Now shocking as it may be, how to do this cleanly with the form_for wasn’t immediately apparent to me. In fact, even the reversely eponymous has_many :through blog was slightly misleading (or incorrect?) on this, going through some, of what seem to me to be, unneccessary work-arounds and stating:

has_many :through won’t work with new records, as it needs saved records with actual ids to use in the foreign keys in the join model.

Thankfully I found this post on Rails forum and was able to adapt the collection.build and field_for techniques to my simple_helpful form)for

Report has_many Locations through Conditions and vice versa.

  # GET /reports/new
  def new
    @page_title = 'Creating Report'
    @report = Report.new()
    #Modify the prepoluation to suit your needs
    Location.find(:all).each do |location|
      @report.conditions.build(:location => location)
    render :action => 'edit'

I am using Markaby not erb for my templates.

#edit.mab (used for new and edit action)
error_messages_for ‘report’
form_for(@report) do |form|
label ‘Week of’, :for => ‘report_week_of’
text form.text_field(:week_of)
@report.conditions.each_with_index do |condition, index|
fields_for “conditions[#{index}]”, condition do |f|
li {
text f.hidden_field(:location_id)
h condition.location.name
text f.text_area(:text)
text form.submit(‘Save »’)

  # POST /reports
  # POST /reports.xml
  def create
    @page_title = 'Creating Report'
    @report = Report.new(params[:report])
    params[:conditions].each_value { |condition| @report.conditions.build(condition)} unless params[:conditions].nil?

And wallah, a neat little form_for, with a has_many :through. Of course, the general technique can be modified for other similar results. If anyone knows a way to clean-up or simplify further, please let me know.

A reminder if you do have advice to post: comment moderation is on, and so are AJAX only comments, so there will be no immediate feedback if you comment. I’ll work on throwing something into the template to let you know it’s been successfully submitted one of these days.