On 6/28/06, Alex Young <alex / blackkettle.org> wrote:
> I'd have SomeAsset and AnotherAsset instances each hold a delegate Asset
> object that they proxy any calls they can't answer themselves to.  I've
> played around a little with trying to implement a full class table
> inheritance scheme for AR on that basis, but never really had enough
> need for it myself to polish it up.

I agree with Alex here. While inheritance was what the tables were
originally trying to emulate, composition was the resulting design
anyways. With Ruby and it's duck typing, inheritance isn't required
for the ability to pass a SomeAsset where an Asset is expected. Java's
static typing would require SomeAsset to "be a" Asset, but Ruby only
needs SomeAsset to "quack like" an asset, which can be covered with
judicious use of composition and method_missing. I'd actually provide
a mixin that sets up this method_missing. Example:

  class Asset < ActiveRecord::Base
    # we're leaving out the reverse relationship to the "child" classes
    # -- just as a parent class implementation wouldn't really know about
    # the classes descended from it

    def xyzzy
      # model specific functionality
    end

    def hidden
      # model specific functionality that's not delegatable
    end

    module Delegator
      AssetDelegates = [ :prop1, :prop1=, :xyzzy ]
      def self.included(other)
        other.module_eval <<-END
          # we use belongs_to rather than has_one because the foreign key
          # is in this table
          belongs_to :asset

          alias :__asset_delegator_old_mm :method_missing
          def method_missing(method, *args, &block)
            if AssetDelegates.include?(method)
              self.asset.send(method, *args, &block)
            else
              __asset_delegator_old_mm(method, *args, &block)
            end
          end
        END
      end
    end
  end

  class SomeAsset < ActiveRecord::Base
    include Asset::Delegator

    def xyzzy
      # override Asset's implementation of xyzzy, feels just like
      # inheritance, except that if we want to call up to the "parent"
      # version, we need to use asset.xyzzy rather than super
    end
  end

  class AnotherAsset < ActiveRecord::Base
    include Asset::Delegator

    # make the hidden method, which wasn't explicitly delegated, available
    # from this "subclass"
    def hidden
      asset.hidden
    end
  end

Jacob Fugal