views:

794

answers:

3

I have multiple classes with many associations linking them together and I would like to be able to take the top-level object, close it, and have all of the child-objects closed as well. I need each object to be closed because I want to be able to pick any parent and have all of it's children closed.

For example (I realize this probably doesn't exist):

class Requisition
  has_many :shipments, :dependent_method => :close
end

class Shipment
  belongs_to :requisition

  has_many :reroutes, :dependent_method => :close
end

class Reroute
  belongs_to :shipment

  has_many :deliveries, :dependent_method => :close
end

class Delivery
  belongs_to :reroute
end

Does anybody know of a good solution to achieve this? A gem/plugin would be perfectly acceptable :-)

Thanks much!

A: 

Its hard to understand your goal. It would help if you could clarify what you mean by "close".

The following information may answer your question.

ActiveRecord has the concept of persistence to the database through the 'save' and 'save!' methods. By default, association objects are saved when a parent object is saved.

Jonathan R. Wallace
So, if the associations are automatically saved, I could possibly override the `before_save` method and check to see if my parent (if one exists) has it's dateClosed attribute set to something other than nil? If so, set mine to it. Does this sound reasonable?
Topher Fangio
P.S. Please see comment to the question above for a better description of 'closing' an object. Thanks!
Topher Fangio
A better approach would be to use association callbacks. An association callback would set the child attribute on association with the parent object.Go here http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html and do a search on "Unsaved objects and associations". You'll need to review the "Association callbacks" section but the previous two sections are relevant too.
Jonathan R. Wallace
From what I read, this only allows you to add a hook when adding or deleting a record, not updating it, I commented below that I will update this question with an answer about a plugin I am writing to handle it.
Topher Fangio
Good point. My recommendation did not take into consideration that the dateClosed would be updated more than once.In that case, I'd go with the before_save technique you first mentioned though it sounds like you see enough functionality there to create a plugin.
Jonathan R. Wallace
A: 

if you never actually delete a row from your DB using the destroy methods you could just overwrite destory methods to do the dateClosed and then i believe :dependent => :destroy just calls the related objects destroy method

def destroy
   dateClosed = Date.today
end

class Requisition  has_many :shipments, :dependent => :destroy
ErsatzRyan
I hadn't considered that, but I actually do need to destroy the objects, so that unfortunately won't work. I'm currently writing my own plugin to handle it and am very close to getting it to work properly. I'll post an update when I am done.
Topher Fangio
A: 

I sat down and wrote a little plugin to do this. I call it acts_as_closable and it simply adds the following method to a before_save filter. You have to use the :autosave => true for each association that you want this to work with, but that made sense to me rather than having acts_as_closable automatically save the association for you. I may make it an option later and release the code. Here is the meat:

def update_closed_status_of_children
  [self.class.reflect_on_all_associations(:has_many), self.class.reflect_on_all_associations(:has_one)].flatten.each do |assoc|

    # Don't consider :through associations since those should be handled in
    # the associated class
    if not (assoc.options.include? :through)
      attribute = self.class.acts_as_closable_config.closed_attribute
      children = self.send(assoc.name)
      children = Array(children).flatten.compact

      children.each do |child|
        # Check to make sure we're only doing this on something declaring acts_as_closable
        if child.class.included_modules.include? ActsAsClosable
          child.send(:closed_value=, self.closed_value)
        end
      end
    end

  end
end

I define some other methods (like :closed_value= and :closed?) but this is the main code I had to figure out. Hope it helps someone else!

Topher Fangio