DEV Community

Janine
Janine

Posted on

Phase 5 Blog - Polymorphic relationships

Introduction

Finishing a software engineering bootcamp feels like a whirlwind—on one hand, there's an overwhelming sense of accomplishment, having made it through the intense learning curve and sleepless nights. But on the other hand, submitting the final project brings a mix of nervousness and uncertainty. The project is a culmination of everything we've absorbed over the past few months: coding languages, frameworks, problem-solving techniques, and best practices. I have learned so much about the languages in the curriculum, JavaScript, React, Python, Flask, Sql, and Flask-SqlAlchemy. It's a reflection of both how far we've come and how much we still have to learn. The joy of completing it is bittersweet, though. There’s a sense of pride in seeing a finished product, but also a realization that the structured learning is over. Now, it's time to face the real world, and the excitement is tempered with the weight of the unknown. But overall, it feels like a pivotal moment—a step into something new.

One final thing I have learned in working with my final project is about Polymorphic relationships. Polymorphic relationships in Python, often seen in object-oriented programming, allow different classes to be treated as instances of the same class through inheritance. This is particularly useful when you have a base class, and several other classes that share common behavior but also have their own unique functionality. In a polymorphic relationship, you can call methods or access properties on objects of the derived classes as if they were objects of the base class, making the code more flexible and reusable. For example, if you have a Shape class with a method draw(), both Circle and Rectangle classes can inherit from Shape and implement their own version of draw(). When you call draw() on any shape object, Python will automatically invoke the correct method for that specific object, based on its actual class. This dynamic behavior is what makes polymorphism so powerful—it reduces the need for redundant code and increases scalability in systems with complex hierarchies or varying behaviors.

An example of polymorphic relationships in Python can be seen with the concept of animals. Let’s say we have a base class Animal, and two derived classes, Dog and Cat, that each implement a method speak(). Despite Dog and Cat being different classes, they both share the common interface of the speak() method, which is defined differently in each class. Here's how it would look:

class Animal:
    def speak(self):
        raise NotImplementedError("Subclass must implement abstract method")

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# Polymorphic behavior in action
animals = [Dog(), Cat()]

for animal in animals:
    print(animal.speak())
Enter fullscreen mode Exit fullscreen mode

In this example, both Dog and Cat are derived from the base class Animal and implement the speak() method in their own way. The list animals holds instances of both Dog and Cat, and when we iterate over the list and call speak(), Python automatically calls the correct version of speak() depending on the actual class of the object—this is polymorphism at work. You’ll get the output:

Woof!
Meow!
Enter fullscreen mode Exit fullscreen mode

This shows how polymorphic relationships let you treat different objects through a common interface, simplifying code that handles a variety of types.


My Project example

In my project I have a class called ‘FamilyMember’ wherein the member_type can either be an instance of the ‘Adult’ or ‘Child’ class. We'll begin by setting up relationships on the ‘Family’ class side and stop there. Here is what I have done on my project:

class FamilyMember(db.Model, SerializerMixin):
    __tablename__ = 'familymembers'
    id = db.Column(db.Integer, primary_key=True)
    family_id = db.Column(db.Integer, db.ForeignKey('families.id'), nullable=False)
    member_id = db.Column(db.Integer, nullable=False)
    member_type = db.Column(ENUM('adult', 'child', name='member_types'), nullable=False)

    serialize_rules = ('-families', )

    def to_dict(self):
        return {
            'id': self.id,
            'family_id': self.family_id,
            'member_id': self.member_id,
            'member_type': self.member_type,
        }

class Family(db.Model, SerializerMixin):
    __tablename__ = 'families'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    invite_code = db.Column(db.String, nullable=False, unique=True)

    adults_member = db.relationship(
        'Adult',
        secondary='familymembers',
        primaryjoin='foreign(FamilyMember.family_id)==Family.id',
        secondaryjoin="and_(FamilyMember.member_type=='adult', foreign(FamilyMember.member_id)==Adult.id)",
        backref=db.backref('familymembers_member', lazy='dynamic'),
        lazy='dynamic',
        viewonly=True,
    )

    children_member = db.relationship(
        'Child',
        secondary='familymembers',
        primaryjoin='foreign(FamilyMember.family_id)==Family.id',
        secondaryjoin="and_(FamilyMember.member_type=='child', foreign(FamilyMember.member_id)==Child.id)",
        lazy='dynamic',
        viewonly=True,
    )

    serialize_rules = ('-events', '-adults', '-children', '-familymembers', '-files')

    def to_dict(self):
        return {
            "id": self.id,
            "name": self.name,
            "adults_member": [adult.to_dict() for adult in self.adults_member],
            "children_member": [child.to_dict() for child in self.children_member],
            "invite_code": self.invite_code
        }

Enter fullscreen mode Exit fullscreen mode

Let us discuss what is happening on the 'adult_member' relationship on the 'Family' class.

First, the relationship connects to the ‘Adult’ model. This means that when we use this relationship, we'll get a list of ‘Adult’ instances, giving us easy access to all the details of every Adult as a family member.
We have to go through the ‘familymembers’ table to get here. Please note that the value for secondary is the ‘familymembers’ table rather than the ‘FamilyMember’ model.
The primaryjoin value is just a standard foreign key pointing at the id of the model we're on.
The secondaryjoin, the connection is facilitated via the combination of a family_id matching the id of the current instance of an Adult or Child and a member_type dictating whether it's an Adult or Child for that family member.
The backref makes things easier by automatically creating a relationship from Family to Adult using the same connections. This means you can use the familymembers_member relationship in the to_dict() method of Post, as well as in routes or other parts of the backend. Follow the rules regarding the ‘Child’ class relationship, then you’re all set.

Please check out my project in github https://github.com/jajaninnin/project-phase5

Please let me know if there are questions, suggestions on how I can improve my code. Thank you for reading.

Sources:
https://hackmd.io/@chrisoney/rkNWNTt_d#Polymorphic-Associations-in-SQLAlchemy
https://docs.sqlalchemy.org/en/20/orm/inheritance.html#concrete-polymorphic-loading-configuration

Top comments (0)