views:

55

answers:

1

I'm trying to implement the chain of responsibility pattern in Ruby and ActiveRecord for a polymorphic object. I'm having a few problems.

  • Sometimes I get an error that a method is not defined when I try to alias_method it, I think this is because the class isn't loaded or something so I explicity do a send to get the method
  • I get a bunch of infinite chains where the aliased function (original_method) calls method which calls original_method. I'm wondering if this is because when you alias a method that's already been overwritten, you're in essence making "original_method" a copy of the aliased method.
  • I'm currently working around this by having a function like "chained" return a sub-class of Setting with all the defined methods but curious why there were so many problems with alias_method right in the class.

Here's an example:

class Hospital
  has_one :setting, :as => :settable
  belongs_to :administrative_area

  def next_link
    adminstrative_area
  end

  def usable_setting
    setting ? setting : next_link.usable_setting
  end
end

Then, I have a Setting object:

class Setting < ActiveRecord::Base

belongs_to :settable, :polymorphic => true

def chained
  %w(api_key active_days).each do |method|

    # this is here because otherwise the method isn't defined,
    # it's almost as while it's going up, the metaclass doesn't have the columns
    # until it loads, probably will be fixed if we cache classes
    self.send method.to_sym

    (class << self; self; end).class_eval do

      define_method method do |*args|
        alias_method "original_#{method}", method
        my_setting = send("original_#{method}")
        if my_setting.nil? or my_setting.empty?
          settable.next_link.usable_setting.chained.send(method)
        else
          return my_setting
        end
      end
    end
  end
  self
end
end
+1  A: 

You seem to be overcomplicating. Seems that you're trying to see if api_key and active_days exists, and if not, get it from somewhere else.

Here's the right way to do it, assuming that api_key and active_days are columns in your table:

class Setting < ActiveRecord::Base

  belongs_to :settable, :polymorphic => true

  def api_key
    super || settable.next_link.usable_setting.api_key
  end

  def active_days
    super || settable.next_link.usable_setting.active_days
  end
end

You can refactor it a bit to keep clarity and remove duplication.

class Setting < ActiveRecord::Base

  belongs_to :settable, :polymorphic => true

  def api_key
    super || next_usable_setting.api_key
  end

  def active_days
    super || next_usable_setting.active_days
  end

  private
  def next_usable_setting
    settable.next_link.usable_setting
  end
end

So in this case notice — if you have api_key/active_days available, it will get returned. Otehrwise, it will go fetch usable_setting from next_link. If that one has api_key/active_days, it will get returned, otherwise it will fetch usable_setting from next_link. Etc.

hakunin
Hey, this answer is great but I think the main problem I was having was with defining the methods metamagically. The reason I want to do it that way is that I have a lot of chained functions and as a programmer would rather spend 10 hours figuring out how to avoid the typing rather than 2 hours just typing it all in. I ended up doing it by creating a separate class that represented the chaining, I think it was hard to do it in activerecord because of the way it creates it's own alias'ed functions.
David
If you want to do it magically, all you have to do is: `[:api_key, :active_days].each{|meth| define_method(meth){super || next_usable_setting.send(meth)}}`. But frankly, I'd rather define them separately, because it reads nicer.
hakunin