GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Rubygem
Description: DataMapper - Core

Simulating polymorphic associations

DataMapper doesn’t support polymorphic associations (associations with one side pointed at an STI subclass) but you can fake it by simulating STI by putting common properties and the association in a base module that you include.

This won’t work:


# This won't work.  Don't try this or you will make DataMapper cry.

class FooBase
  include DataMapper::Resource

  property :id, Serial # primary key
  property :some_common_property, String

  property :type, Discriminator # enables STI

  belongs_to :bar
end

class FooSubclassA < FooBase
  property :some_property_only_subclass_a_has, String
end

class FooSubclassB < FooBase
  property :some_property_only_subclass_b_has, Integer
end

class Bar
  include DataMapper::Resource

  has n, :foo_subclass_as
  has n, :foo_subclass_bs
end

This looks like a perfectly reasonable thing to do, but the moment you try to access Bar#foo_subclass_as or Bar#foo_subclass_bs, it’ll fail with a long ugly backtrace.

What you can do, however, is give up STI, keep its benefits, and make the associations work with something like this:


module FooBase # NOTE: it's a Module, _not_ a Class
  def self.included(other)
    other.class_eval <<-EOS
      property :id, Serial
      property :some_common_property, String

      belongs_to :bar
    EOS
  end
end

class FooSubclassA # NOTE: not a subclass of anything now
  include DataMapper::Resource
  include FooBase

  property :some_property_only_subclass_a_has, String
end

class FooSubclassB # NOTE: not a subclass of anything now
  include DataMapper::Resource
  include FooBase

  property :some_property_only_subclass_b_has, Integer
end

class Bar
  include DataMapper::Resource

  has n, :foo_subclass_as
  has n, :foo_subclass_bs
end

FooSubclassA and FooSubclassB are no longer formally related, in the sense of having a common ancestor class (apart from Object, but you know what I mean). They are stored in separate tables in the database, whereas STI would by definition keep them in the same table. However, they are guaranteed to have the common properties defined in FooBase, which becomes a kind of de facto superclass for them, and more importantly your associations now work.

You can take this even further. dkubb has suggested that you create a base module with properties such as your models’ primary key and magic timestamps. In other words, you get the benefits of STI (a guarantee of certain properties with one central definition for them) without the drawbacks.

Last edited by phildarnowsky, about 1 month ago
Versions: