Approximatng Polymorphic Inheritance with Elixir

In DrProject’s new ticketing system, the default ticket will be nothing more than a todo list, which the user can then extend however she wishes; the tickets will grow with the project.

When a user wants to add a new field to her project’s tickets, she has to give us some information: the title of the new field, any default values, and the type of data the new field is going to store. We take this information and turn it into a Field object, which gets flushed to the database and always shows up whenever anyone looks at that project’s tickets.


class Field(Entity):
    has_field('title', Unicode())
    has_field('type', Integer)
    has_field('default', Unicode())    
    belongs_to('project', of_kind='Project')

What a Field object doesn’t store is any actual ticket data; that task is left up to a class that holds the specific data type (remember, we’re dealing with a database–not just Python–so types matter). We want to provide a decent selection of canonical types to choose from, so we’re at least going to have a string type, a date/time type, an enumeration, and float and integer types. Creating each of these classes means a lot of repetition; each class has one special data field, and then it has to reference the Ticket and the Field it belongs to.


class FieldDataString(Entity):
    has_field('data', Unicode())
    belongs_to('ticket', of_kind='Ticket')
    belongs_to('field', of_kind='Field')

class FieldDataDatetime(Entity):
    has_field('data', DateTime)
    belongs_to('ticket', of_kind='Ticket')
    belongs_to('field', of_kind='Field')

…and so on.

The normal solution: create a base class for all the common features and then have each subclass handle its special data type. However, we’re dealing with an ORM so inheritance doesn’t work the way it’s supposed to; I tried it, and all my derived classes got smooshed into a single table along with the base class (elixir list posting).

Up to this point, we’ve been busy figuring out the problems with our model, so I just accepted the redundancy and worked with it. Now that we have a handle on where we are going, Greg wants us to extend the model. This means that changes need to be made to each specific type, which is not only tedious, but a great way to introduce bugs. I decided to have another go at inheritance, so we can specify all the commonality in one place.

Examining the elixir source (which is lovely–great job guys), I found that the derived types were getting smooshed in with the base class because of the way elixir sets up relationships. When a belongs_to relationship is declared, elixir stuffs it into the correct class’s __dict__ by pulling out a frame object from the call stack. The relationship has no way of knowing what class it’s supposed to go into; it’s blindly pushed into the first one it finds.

Inspired by all the metaclass magic going on in elixir, I decided to try out some of my own. Normally, a relationship is attached during the class definition time, and then all the relationships are processed in the EntityMeta metaclass. Instead of using the basic metaclass, I declared my own derived metaclass, which attaches the proper relationship objects to each class and then sends it off to elixir for processing.


class FieldDataMeta(EntityMeta):
    """ Sets up common relationships for FieldData*Type* classes """
    def __init__(cls, name, bases, dict_):
        if bases[0] is object:
            return # don't process FieldData base class

        # Pull the existing statements, or create an empty list
        if not hasattr(cls, STATEMENTS):
            setattr(cls, STATEMENTS, [])
            statements = getattr(cls, STATEMENTS)

        # Add the FieldData* relationships
        ticket_rel = (belongs_to, ('ticket',), dict(of_kind='Ticket'))
        field_rel = (belongs_to, ('field',), dict(of_kind='Field', lazy=False))
        statements.extend([ticket_rel, field_rel])

        EntityMeta.__init__(cls, name, bases, dict_)

It’s not inheritance in the way that I expected, but it gets rid of the redundancy and simplifies the code a bit, so I’m happy.

The full implementation is available here.

Advertisements

2 Responses to “Approximatng Polymorphic Inheritance with Elixir”

  1. The Third Bit » Blog Archive » Polymorphic Inheritance in a Relational Database Says:

    […] for DrProject that will allow users to add new fields to tickets on the fly.  Jeff just posted this description of their design, which I think is a pretty elegant solution.  We’d be grateful for comments […]

  2. cheap rowing machines Says:

    It’s really a great and helpful piece of
    information. I am happy that you just shared this helpful info with us.
    Please keep us informed like this. Thank you for sharing.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: