views:

501

answers:

1

I am developing a complex form that updates several records of one model at once, whilst simultaneously updating an associated model. It looks a bit like this:

class Sport
  has_one :photo
end

class Photo
  belongs_to :sport
  acts_as_fleximage
end

class Page
  # the page is not related to either of the previous models
end

Just for a bit of background info, the Page model is a generic model for which the users will be able to create as many as they like (a CMS). In addition, they are given a small number of compulsory "system" pages when they sign up. When they try to edit a system page, the form is slightly different from the generic page form.

One of the system pages is a "Sports" page. Where they can add some text for each of their sports (saved in the "sport" model) and upload a photo (saved in the "photo" model).

I've crafted a form which seems to be doing the trick. I won't post the view, but here is an example of the parameters it sends:

:id => 1
:page => {"title"=>"Our sports"}
:sport => {
  "1" => {
    "description" => "<p>I love playing hockey...</p>"
    "photo_attributes" => {
      "image_file" => #<File:/tmp/RackMultipart20100126-955-k0gxu8-0>,
      "description" => "Me in my hockey kit"
    }
  },
  "2" => { #more of the same}
}

Now, to save all this, my controller/action looks something like this:

def update_sports_page
  @page = Page.find params[:id]
  @page.update_attributes params[:page]
  Sport.update(params[:sport].keys, params[:sport].values)
  redirect_to #etc
end

Now when I edit the sports page, everything saves and updates correctly, EXCEPT, if I change the photo then rather than it updating the existing record in the database, it just creates a new record and sets the sport_id of the old record to NULL.

So eventually, after many edits, there is a huge number of orphan records in the database.

Can anyone spot what I am doing wrong here?

(ps, in case it's relevant, I'm using fleximage on the Photo model)

+1  A: 

That's probably the correct behavior, since that association is set :dependent => :nullify by default, not :dependent => :destroy.

It might be possible to fix it with:

class Photo
  belongs_to :sport,
    :dependent => :destroy
end

That should remove orphan records automatically for you.

You should also be careful to catch exceptions when performing any find or update operation.

def update_sports_page
  @page = Page.find params[:id]
  @page.update_attributes params[:page]

  params[:sport].each do |sport_id, sport_params|
    sport = Sport.find(sport_id)
    sport.update_attributes!(sport_params)
  end

  redirect_to #etc
rescue ActiveRecord::RecordNotFound
  render(:partial => 'page_not_found', :status => :not_found)
rescue ActiveRecord::RecordInvalid
  render(:action => 'edit')
end

That's an example. The edit and update method should have a common "sport loader" mechanism which can handle retrieving all the relevant records for the page instead of having to duplicate this functionality during update. It's responsible to catch errors on update and display them on the edit page for review.

tadman