views:

69

answers:

1

Hello,

I am trying to DRY up my code a bit so I am writing a method to defer or delegate certain methods to a different object, but only if it exists. Here is the basic idea: I have Shipment < AbstractShipment which could have a Reroute < AbstractShipment. Either a Shipment or it's Reroute can have a Delivery (or deliveries), but not both.

When I call shipment.deliveries, I want it to check to see if it has a reroute first. If not, then simply call AbstractShipment's deliveries method; if so, delegate the method to the reroute.

I tried this with the simple code below:

module Kernel
  private
    def this_method
      caller[0] =~ /`([^']*)'/ and $1
    end        
end

class Shipment < AbstractShipment
  ...

  def deferToReroute
    if self.reroute.present?
      self.reroute.send(this_method)
    else
      super
    end
  end

  alias_method :isComplete?, :deferToReroute
  alias_method :quantityReceived, :deferToReroute
  alias_method :receiptDate, :deferToReroute
end

The Kernel.this_method is just a convenience to find out which method was called. However, calling super throws

super: no superclass method `deferToReroute'

I searched a bit and found this link which discusses that this is a bug in Ruby 1.8 but is fixed in 1.9. Unfortunately, I can't upgrade this code to 1.9 yet, so does anyone have any suggestions for workarounds?

Thanks :-)

Edit: After a bit of looking at my code, I realized that I don't actually need to alias all of the methods that I did, I actually only needed to overwrite the deliveries method since the other three actually call it for their calculations. However, I would still love to know y'all's thoughts since I have run into this before.

+1  A: 

Rather than using alias_method here, you might be better served by hard-overriding these methods, like so:

class Shipment < AbstractShipment
  def isComplete?
    return super unless reroute
    reroute.isComplete? 
  end
end

if you find you are doing this 5-10 times per class, you can make it nicer like so:

class Shipment < AbstractShipment
   def self.deferred_to_reroute(*method_names)
     method_names.each do |method_name|
       eval "def #{method_name}; return super unless reroute; reroute.#{method_name}; end"
     end
   end
   deferred_to_reroute :isComplete?, :quantityReceived, :receiptDate
end

Using a straight eval offers good performance characteristics and allows you to have a simple, declarative syntax for what you are doing within your class definition.

austinfromboston
Awesome! If I had played around with it for a little bit longer, I would have attempted something like this since I have been writing a plug-in that does something similar, but I doubt it would have been as clean. Thanks very much :-)
Topher Fangio