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
  1. evan about 10 hours later:

    Ah… .delete_if is a method on Enumerable, maybe, and not overridden by AssociationProxy like regular .delete. That would explain why you can abuse it in such a way. You can probably use .target.delete to get the real .delete. When in doubt, Marshal.dump the object and look at the first 100 chars to see the class name. The .class method lies—it too is proxied.

  2. Tim Connor about 10 hours later:

    Ya, I had checked out the core mailing list and it didn’t sounds like they were adding delete_if overriding anytime soon, so I figured it was a reasonably safe hack.

    Thanks for filling in more details, evan.

  3. Yossef 20 days later:

    I don’t know if you saw my particular post about delete_if, but I am one of the people annoyed about it not working as I expect. I mean, I’ve come to see it as doing the same thing delete does with some logic attached to it, but Rails changes that.

    I often do things that work very well with ORM, but as you said, it can be problematic. When that’s the case, you obviously want to get everything ready, to stage the new version somewhere (like memory), and then push it all at once. I just don’t know if commit_delete_if_children is the way to do it.

  4. Tim Connor 20 days later:

    Ya, I saw your post. As you can gather, I’m now glad of the extra flexibility.

    It’s not the most elegant way it could be done, no. It’s just the one that doesn’t require reworking a bunch of ActiveRecord code, which is some of the more complicated code in Rails. Also, you wouldn’t want to use this on an overly large set of records, obviously. Ideally you wouldn’t need this sort of manual callback and AR would just handle it all exactly like you’d want, but unfortunately “Do what I mean” is one of the harder magic functions to write, in any language.

    I actually had already reworked my domain model a bit so this technique wasn’t required, by the time I figured it out.

Comment form

(leave url/email »)

Help with Textile (code)