In memory delete from an rails Association - a counterpart to build 4

Posted by Tim Connor Tue, 17 Apr 2007 21:15:00 GMT

I don’t like how a lot of the association methods automatically go through to the DB. I like to be able to work with my objects in memory more, get them actually how I want, and then save to the DB. There are times that having instant DB saving go through can really mess with a object that is live on a site. For creation there is such a method: build. And for editing: an update won’t go through until you save it. There isn’t an obvious counterpart for deletion.

I found one by googling around for something else, and noticing people complaining about delete_if not working as they expected. delete_if does exactly what I want, operate on the collection without effecting the DB. Of course, this doesn’t do much good without a way to save it when you are ready. The easiest way I’ve come up with so far is to keep a copy of the initial ids array, and then compare to that later.

class Parent < ActiveRecord::Base
  has_many :children
  attr_accessor :initial_children_ids

  def commit_delete_if_children
    parent.connection.delete <<-SQL, "Delete_ifing children"
      DELETE FROM children WHERE id IN ('#{(initial_children_ids - children_ids).join('\',\'')}')
    SQL
  end

  protected
  def after_initialize
    self.initial_children_ids = self.children_ids
  end
end

>> dad.children.delete_if { |child| child.id == 3}
>> dad.commit_delete_if_children
=> 1
>> dad.commit_delete_if_children
=> 0

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)
    end
    render :action => 'edit'
  end

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)
ul{
@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
br
text f.text_area(:text)
}
end
end
}
text form.submit(‘Save »’)
end

  # 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.