HABTM: join table records being deleted & not restored when validation fails

Rails: 2.1.2 Ruby: 1.8.6

I have two models joined by HABTM: Sessions and Learners. On the Session edit form, there's a checkbox list of Learners. Users check which Learners will attend the session. Very straight forward.

One of the business rules is that a Session must have at least one learner. We're enforcing that with a validation procedure in the Session model.

validate :has_at_least_one_learner?

def has_at_least_one_learner?   if (self.learners.empty?)     errors.add_to_base("At least one learner must be selected.")   end end

In the Session controller, we have this code for the Update action:

@session = @project.sessions.find(params[:id]) params[:session][:learner_ids] ||=

if @session.update_attributes(params[:session]) ... successful update else ... bad update end

All pretty simple. And, indeed, if you try to save a Session without any learners selected, you get the correct validation error.

But what I'm seeing is that even though Rails is displaying the error message, it's still deleting the associated records in the join table, learners_sessions.

What I'd like is for Rails to restore the deleted join records if the update fails. I thought that using transactions would do the trick, but so far I haven't had any success there either.

@session = @project.sessions.find(params[:id]) params[:session][:learner_ids] ||=

@session.transaction do   begin     @session.update_attributes!(params[:session])   rescue ActiveRecord::RecordInvalid     -- bad update   else     -- good update   end end

Any ideas? How can I get the validation error message to display and still keep my records in the join table?

... what I'm seeing is that even though Rails is displaying the error message, it's still deleting the associated records in the join table, learners_sessions.

Though I have not used it, there seems to be a plugin that addresses this kind of issue:

http://code.google.com/p/habtm-with-deferred-save/

Good luck!

params[:session][:learner_ids] ||=

if @session.update_attributes(params[:session]) ... successful update else ... bad update end

what I'm seeing is that even though Rails is displaying the error message, it's still deleting the associated records in the join table, learners_sessions.

What I'd like is for Rails to restore the deleted join records if the update fails. I thought that using transactions would do the trick, but so far I haven't had any success there either.

I may be missing a piece of the jigsaw; but instead of putting a blank array into the params[:session][:learner_ids] if it's nil, why don't you populate it with the existing ids? So Rails will either use the newly selected ids from the form, of repopulate with the old ones?

UI wise, that ends up being a bit awkward. The user thinks they've deselected all learners, but if they edit the page again, all the learners are still selected.

After a little bit of testing, this plugin seems to solve the problem. Thanks.

Sheesh. The save the learners in an array, and restore them yourself on any failure to update

  old_learners = @session.learners.map {|learner| learner.id}

  if @session.update_attributes(params[:session])   ... successful update   else   ... bad update     @session.update_attributes{:learner_ids => old_learners}   end

But seeing as you've got the plugin doing what you need, it's academic.