wiki:S3/S3Model/SuperEntities

Version 28 (modified by Dominic König, 7 years ago) ( diff )

--

Super-Entities

Introduction

Sometimes it is useful to link the same component to multiple master entities without introducing a separate foreign key constraint for each master.

Example: both warehouses and offices have contact persons. Instead of creating separate warehouse_contact and office_contact components, you can make use of the super-entity concept to link a single site_contact component to both entities.

To achieve this, S3 supports a special type of key tables called Super-Entities (referring to the fact that they constitue a generalization concept):

Super-Entity Relationships

A super-entity stores (tablename, uuid) pairs which identify records across multiple tables, and the primary key of the super-entity (super-key) can then be used for foreign key constraints to link components. The primary key of the super-entity is usually referred to as super-key, and the respective foreign key constraints are called super-link.

The super-entity concept is implemented in S3Model, and supported by both S3CRUD and S3Import. Custom methods can make use of the current.s3db API to handle super-entities as described below.

API

To simplify the handling of super-entities in the model, S3Model provides a unified API for super-entities:

Defining a Super-Entity

A super-entity can be defined using the super_entity() method of S3Model:

class MyModel(S3Model):

    def model(self):

        # Define a dict with nice names for the instance types:
        my_instance_types = {
            "my_instance_tablename": current.T("Nice name for the instance type"),
        }

        # Define the super-entity
        tablename = "my_super_entity"
        table = self.super_entity(tablename, 
                                  "my_super_id",                   # specify the super ID
                                  my_instance_types,               # specify the instance types
                                  Field("start_date", "datetime"), # shared field
                                  location_id())                   # shared field

Shared fields will mirror the values from the respective instance record, which allows easy access without need to join multiple tables.

If the names of shared fields are different in certain instance tables, you can define a field mapping when you configure the instance table (continuing the example from above):

        # Define the instance table
        tablename = "my_instance_table"
        table = self.define_table(tablename,
                                  Field("first_date", "datetime"),
                                  Field("last_date", "datetime"),
                                  *s3_meta_fields())

        # Configure the instance table
        self.configure("my_instance_table",
                       # Configure the super entity
                       super_entity = "my_super_entity",
                       # Map the "first_date" field in my_instance_table to "start_date" in my_super_entity
                       my_super_entity_fields = {"start_date": "first_date"},
                  )

Defining an Instance of a Super-Entity

To make a table an instance of a super-entity, you can use s3db.configure():

s3db.configure(table, super_entity = db.sit_situation)

By default, all fields that the table and the super-entity have in common will be mirrored in the super-entity ("shared fields"). You can override this by specifying a list of fields to be mirrored by the super-entity.

s3db.configure(table,
               super_entity = db.sit_situation,
               sit_situation_fields = ["datetime"])

In case your table uses different names for the shared fields, you can use a dict instead to specify a mapping:

s3db.configure(table,
               super_entity = db.sit_situation,
               sit_situation_fields = dict(datetime="timestmp", location_id="location_id"))

Linking to a Super-Entity

Both, instance tables as well as shared components need to be linked to the super-entity. This can be done by inserting a super-key field into the instance/component table which must have the same name as the primary key of the super-entity. You can use the super_link() function as a DRY method for this:

tablename = "pr_presence"
table = db.define_table(tablename,
                        super_link("pe_id", "pr_pentity"),
                        super_link("sit_id", "sit_situation"), # sit_id
                        ...)

Note: super_link generates a Field instance - if you just need the name of the super-key field, you can use:

sk = s3db.super_key(db.pr_pentity) # returns the string "pe_id"

Updating a Super-Entity

If you use s3_rest_controller() for CRUD, it will automatically create, update and delete super-entities as necessary.

Otherwise, or if you manipulate records outside s3_rest_controller, you have to update all super-entities which an instance table implements whenever you create, update or delete a record in that table.

To do this, you can use:

s3db.update_super(table, record)

where table is the instance table and record the newly created/updated record.

In case you're going to delete a record from an instance table, you may use:

s3db.delete_super(table, record)

before you delete the record, where table is the instance table and record the record to be deleted.


DeveloperGuidelines

Attachments (1)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.