Relationships and Serialization

How to create healthy boundaries

Relationships and Serialization

Andy has a toy, Buzz Lightyear. Buzz has an owner, Andy. And Andy has a toy Buzz. And Buzz belongs to Andy...

Tim, has a photo they posted. That photo has likes. Those likes have owners. One of the likes belongs to Tim (yes he liked his own photo). And Tim has a photo he posted...

If you're still around, you'll realize that an infinite loop is going on over here. If you don't want to be stuck here forever - or tcause your program to crash - you may have to utilize something called "serialize rules".

But let's go back a few steps.


Model Attributes

Let's talk about models. When creating an application, you're obviously going to want to be able to access a model's attributes. For example, you'll want to be able to access a user's name.

That's pretty simple to do!

Here is my user model and its attributes:

class User(db.Model, SerializerMixin):
    __tablename__ = 'users'

    # Columns
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String)
    lastname = db.Column(db.String)
    email = db.Column(db.String, unique=True)
    _password_hash = db.Column(db.String, nullable=False)

To access a user's first name, after fetching the user, all I need to do is:

userFirstName = user.firstname

Relationships

In my website, my users have the ability to post toys that they recommend. So every user owns toys they posted. However, the toys are not an attribute of the User model. They have their own model: Toy.

So how would I access a user's toys?

We'd have to make it clear to the User model that there's a relationship over here.

Let's create one!

# Relationships
toys = db.relationship('Toy', back_populates='user', cascade='all, delete-orphan')

I'm giving the user an attribute of sorts called "toys", which is assigned a relationship with the Toy model.

To access a user's toy array, now all I need to do is:

usersToys = user.toys

The Problem

Relationships can be complicated.

See, Andy posted toys, such as a Buzz Lightyear figurine. And each toy, like Buzz, has a user that posted it, like Andy. And Andy has Buzz Lightyear toy. And around we go.

There is no way your backend can load such data without crashing, because that would just go on for infinity.


The Solution

Relationships can be complicated, but with some healthy boundaries, we can still keep them. All we need are some rules.

The first thing we need to make sure is that our model is inheriting from the "SerializerMixin" class.

from sqlalchemy_serializer import SerializerMixin
# Some code

class User(db.Model, SerializerMixin):
# Rest of my code

Then we create some serialize rules. These are where you'll indicate all of the attributes you want to exclude, so that the train eventually stops somewhere.

In this case, I want to exclude "user.toys.user", because that leads into "user.toys.user.toys.user.toys..."

Here's how I'll do it:

# Serialize Rules
serialize_rules = ('-toys.user',  '-toys.user_id',)

The Big Picture

Now generally, your models will have more than just one attribute. To create your serialize rules, you go through each attribute and exclude it where it will come back to your model.

(As an aside, you also have the option of choosing which attributes to INCLUDE, at the exclusion of all others.)

Here is my model with all of the serialization necessary to stop my application from crashing, as well as some data that was unnecessary:

class User(db.Model, SerializerMixin):
    __tablename__ = 'users'

    # Columns
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String)
    lastname = db.Column(db.String)
    email = db.Column(db.String, unique=True)
    _password_hash = db.Column(db.String, nullable=False)
    profile_picture = db.Column(db.String, nullable=True, default="https://t3.ftcdn.net/jpg/04/43/94/64/360_F_443946416_l2xXrFoIuUkItmyscOK5MNh6h0Vai3Ua.jpg")
    bio = db.Column(db.String, nullable=True, default="Proud member of the flock!")
    country = db.Column(db.String, nullable=False)

    # Some code

    # Relationships
    toys = db.relationship('Toy', back_populates='user', cascade='all, delete-orphan')
    reviews = db.relationship('Review', back_populates='user', cascade='all, delete-orphan')

    # Serialize Rules
    serialize_rules = ('-toys.user', '-toys.reviews.user', '-reviews.user', '-reviews.toy', '-toys.user_id', '-toys.age_ranges', '-_password_hash')

    # Some more code

You see, even Buzz Lightyear needed some limits. There's a reason "boundaries" is a buzz word.

To limits... and function.