Skip to content
namlook edited this page Aug 30, 2011 · 2 revisions

Inheritance and Polymorphism

MongoLite support inheritance and polymorphism.

Inheritance

If a document inherites from another document, its structure (skeleton and optional) will be updated to match its parents' structure as well. This work too with default_values:

@connection.register
class User(Document):
    __database__ = "tutorial"
    __collection__ = "users"
    skeleton = {
        "name": unicode,
        "is_registered": bool,
    }
    optional = {
        "age": int,
    }
    default_values = {"is_registered": False}

class ProfessionalUser(User):
    skeleton = {
        "activity": unicode,
    }

The skeleton is updated:

>>> user = connection.User()
>>> user
{"name": None, "is_registered": False}

>>> pro = connection.ProfessionalUser()
>>> pro
{"name": None, "is_registered": False, "activity": None}

Inherited queries

When we query a document, we get all documents of the same instance:

>>> users = connection.User.find() # get User instances only

The issue here is that if the fetched document is a ProfessionalUser (ie, it contains the activity field), it will be wrapped into a User instance, not a ProfessionalUser instance. This is a huge issue if we need to access to method related to ProfessionalUser.

The solution is to add the _type field into the skeleton:

@connection.register
class User(Document):
    __database__ = "tutorial"
    __collection__ = "users"
    skeleton = {
        "_type": unicode,
        "name": unicode,
        "is_registered": bool,
    }
    optional = {
        "age": int,
    }
    default_values = {"is_registered": False}

class ProfessionalUser(User):
    skeleton = {
        "activity": unicode,
    }

    def professional_stuff(self):
        return "stuff"

This will add the document class name into the _type field so MongoLite will know how to wrap the fetched document:

>>> user = connection.User()
>>> user
{"_type": "User", "name": None, "is_registered": False}
>>> user["name"] = u"Timy"
>>> user.save()

>>> pro = connection.ProfessionalUser()
>>> pro
{"_type": "ProfessionalUser", "name": None, "is_registered": False, "activity": None}
>>> pro["name"] = u"Bob"
>>> pro["activity"] = u"Alien"
>>> pro.save()

Now MongoLite will be able to wrap the correct document:

>>> user = connection.User.find_one({"name":"Bob"})
>>> user.professional_stuff()
"stuff"

FAQ

Hey ! I already use the _type field. Don't touch it !

No problem. Just overwrite the type_field attribute:

@connection.register
class User(Document):
    type_field = "_t"
    skeleton = {
        "_type": unicode,
        "_t": unicode,
        "name": unicode,
    }

If you don't planed to use inherited queries, just set type_field to None;

What about polymorphism ?

In the following example, we have to object A and B wich herite from Root. And we want to build an object C from A and B. Let's build Root, A and B first :

class Root(Document):
    skeleton = {
       "root": int,
   }
   default_values = {"root": 42}

class A(Root):
   skeleton = {
       "a_field": unicode,
   }
   default_values = {"a_field": "hello world"}
class B(Root):
    skeleton = {
        "b_field": unicode,
    }

Polymorphisme just work as expected:

class C(A,B):
   skeleton = {
       "c_field": float
   }
>>> c = C()
>>> c == {'b_field': None, 'root': 42, 'c_field': None, 'a_field': "hello world"}
True
>>> C.default_values
{"root": 42, "a_field": "hello world"}