rm table

debugging the tests/table1 shortcode

Look up the scenario you need to implement on the left, then use the template implementation on the right in your code.

Relationship Scenario Example Python Implementation

Implementing one to one relationships between class X and Y

#1.

1 → 1, directional, all methods on X

  Singular API             No API
 ______________        ______________
|       X      |      |       Y      |
|______________|      |______________|
|              |      |              |
|void  setY(y) |1    1|              |
|Y     getY()  |⎯⎯⎯⎯⎯⎯⎯⎯⎯→|              |
|void  clearY()|      |              |
|______________|      |______________|
        

Note: The clearY() implementation needs to get a reference to y in order to call remove_rel(x, y, ...) which is done by calling getY() on itself.

class X:
  def __init__(self): 
    rm.enforce("xtoy", "onetoone", "directional")
	
  def setY(self, y): 
    rm.add_rel(self, y, "xtoy")
	
  def getY(self): 
    rm.find_target(source=self, rel_id="xtoy")
	
  def clearY(self): 
    rm.remove_rel(self, self.getY(), "xtoy")
				
class Y:
  pass
				
#2.

1 → 1, directional, all methods on Y

    No API              Singular API
 ______________        ______________
|       X      |      |       Y      |
|______________|      |______________|
|              |      |              |
|              |1    1| setX(x)      |
|              |⎯⎯⎯⎯⎯⎯⎯⎯→ | getX()       |
|              |      | clearX()     |
|______________|      |______________|
				
class X:
  pass
				
class Y:
  def __init__(self): 
    rm.enforce("xtoy", "onetoone", "directional")

  def setX(self, x):
    rm.add_rel(x, self, "xtoy")

  def getX(self):
    rm.find_source(target=self, rel_id="xtoy")

  def clearX(self):
    rm.remove_rel(self.getX(), self, "xtoy")
				
#3.

1 ←→ 1, bi-directional, methods on both X and Y

   Singular API         Singular API
 ______________        ______________
|       X      |      |       Y      |
|______________|      |______________|
|              |      |              |
|void  setY(y) |1    1| setX(x)      |
|Y     getY()  | ←⎯⎯⎯⎯→ | getX()       |
|void  clearY()|      | clearX()     |
|______________|      |______________|
				
class X:                    
  def __init__(self):
    rm.enforce("xy", "onetoone", "bidirectional")
  
  def setY(self, y):
    rm.add_rel(self, y, "xy")
  
  def getY(self):
    rm.find_target(self, "xy")
  
  def clearY(self):
    rm.remove_rel(self, self.getY(), "xy")
				
class Y:
  def __init__(self):
    rm.enforce("xy", "onetoone", "bidirectional")

  def setX(self, x):
    rm.add_rel(self, x, "xy")

  def getX(self):
    rm.find_target(self, "xy")

  def clearX(self):
    rm.remove_rel(self, self.getX(), "xy")
				
#3A.

1 ←→ 1, bi-directional, methods on both X and Y

Alternative implementation of scenario 3, using "directional" and a backpointer method

diagram as above
        				
class X:                    
  def __init__(self):
    rm.enforce("xy", "onetoone", "directional")  # different to 3.
    # uses 'directional' not 'bidirectional'

  def setY(self, y):
    rm.add_rel(self, y, "xy")  # same as 3.
  
  def getY(self):
    rm.find_target(self, "xy")  # same as 3.
  
  def clearY(self):
    rm.remove_rel(self, self.getY(), "xy")  # same as 3.
				
class Y:
  def __init__(self):
    rm.enforce("xy", "onetoone", "directional")  # different to 3.
    # uses 'directional' not 'bidirectional'
    # redundant call since already called in X's constructor

  def setX(self, x):  # different to 3.
    rm.add_rel(self, x, "xy")
    # source and target params swapped

  def getX(self):  # different to 3.
    rm.find_source(self, "xy")
    # uses 'find_source' not 'find_target'

  def clearX(self):  # different to 3.
    rm.remove_rel(self, self.getX(), "xy")
    # source and target params swapped
				

Notes on Scenario 3 and 3A:

  1. When you create a bi-directional enforcement rule (Scenario 3) with a call to rm.enforce("xy", "onetoone", "bidirectional") you are actually causing rm to create two relationship entries in the rm. This means you can reliably use a rm.find_target(source=self) call from either side, knowing there is a relationship in both directions.

  2. When you create a directional enforcement rule (Scenario 3A) with a call to rm.enforce("xy", "onetoone", "directional") or leave out this call altogether, you are causing rm to create only the relationships that you ask for. Thus classes on the 'target' side of a relationship cannot call rm.find_target(source=self) to find out who is pointing to them. They can however, thanks to the magic of rm, call rm.find_source() to derive this information.

  3. This means bidirectional relationships never actually need to be used or declared, 😲, since an implicit back-pointer (i.e. a back reference) is always deducible using rm.find_source(), when using a Relationship Manager! In fact a bidirectional relationship creates extra entries in the rm datastructure, and slightly more overhead in performance (maintaining both relationships e.g. in the case of creation and removal).

    However you may still want to declare a bidirectional relationship for its semantic value in your particular business logic domain, or for domain modelling accuracy - or even just for your own implementation preferences.

Implementing one to many relationships between class X and Y

#4.

1 → *, directional, all methods on X

  Plural  API             No API
 _____________        ______________
|      X      |      |       Y      |
|_____________|      |______________|
|             |      |              |
|addY(y)      |1    *|              |
|getAllY()    | ⎯⎯⎯⎯⎯⎯→ |              |
|removeY(y)   |      |              |
|_____________|      |______________|
				
class X:
  def __init__(self):
    rm.enforce

  def addY(self, y):
    rm.add_rel(self, y, "xtoy")

  def getAllY(self):
    rm.find_targets(self, "xtoy")

  def removeY(self, y):
    rm.remove_rel(self, y, "xtoy")
				
class Y:  # no methods on rhs
  pass
				
#5.

1 ←→ *, bi-directional, methods on both X and Y

  Plural  API          Singular API
 _____________        ______________
|      X      |      |       Y      |
|_____________|      |______________|
|             |      |              |
|addY(y)      |1    *| setX(x)      |
|getAllY()    | ←⎯⎯⎯⎯→ | getX()       |
|removeY(y)   |      | clearX()     |
|_____________|      |______________|
        
  • X has the required plural API
  • Y has the reciprocal singular API

Since there are two API's, one on each class, this makes it a bidirectional relationship.

However - there still remains a sense of directionality because the one to many is directional i.e. the the lhs. 'one' side is the X and the rhs. 'many' side is the Y, not the other way around.

class X:
  def __init__(self):
    rm.enforce("xtoy", "onetomany", "bidirectional")

  def addY(self, y):
    rm.add_rel(self, y, "xtoy")

  def getAllY(self):
    rm.find_targets(self, "xtoy")

  def removeY(self, y):
    rm.remove_rel(self, y, "xtoy")
				
class Y:
  # though bi, there is still a direction!
  def setX(self, x):
    rm.add_rel(x, self, "xtoy")

  def getX(self):
    rm.find_target(self, "xtoy")

  def clearX(self):
    rm.remove_rel(self, self.getX(), "xtoy")
				
#5A.

1 ←→ *, bi-directional, methods on both X and Y

Alternative implementation of scenario 5, using "directional" and a backpointer method

diagram as above
                
class X:
  def __init__(self):
    rm.enforce("xtoy", "onetomany", "directional")  # different to 5
    # uses 'directional' not 'bidirectional'

  def addY(self, y):
    rm.add_rel(self, y, "xtoy")  # same as 5.

  def getAllY(self):
    rm.find_targets(self, "xtoy")  # same as 5.

  def removeY(self, y):
    rm.remove_rel(self, y, "xtoy")  # same as 5.
      
class Y:
  def setX(self, x):
    rm.add_rel(x, self, "xtoy")  # same as 5.

  def getX(self):
    rm.find_source(self, "xtoy")  # different to 5
    # uses 'find_source' not 'find_target'

  def clearX(self):
    rm.remove_rel(self.getX(), self, "xtoy")  # different to 5
    # source and target params swapped
      

Implementing many to one relationships between class X and Y

#6.

* → 1, directional, all methods on Y

    No API              Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
|              |*    1|addX(x)       |
|              | ⎯⎯⎯⎯⎯⎯→ |getAllX()     |
|              |      |removeX(x)    |
|______________|      |______________|
				

DRAFT API (not tested)

class X:
  pass
				
class Y:
  def addX(x) -> None:
    rm.add_rel(x, this, "xtoy")

  def getAllX() -> List:
    return rm.find_sources(this, "xtoy")

  def removeX(x) -> None:
    rm.remove_rel(x, this, "xtoy")
				

#7.

* ←→ 1, bi-directional, methods on both X and Y

  Singular API          Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
|void  setY(y) |*    1|addX(x)       |
|Y     getY()  | ←⎯⎯⎯⎯→ |getAllX()     |
|void  clearY()|      |removeX(x)    |
|______________|      |______________|
				

DRAFT API (not tested)

class X:
  def setY(y) -> None:
    rm.add_rel(this, y, "xtoy")

  def getY() -> Y:
    rm.find_target(this, "xtoy")

  def clearY() -> None:
    rm.remove_rel(this, getY(), "xtoy")
				
class Y:
  def addX(x) -> None:
    rm.add_rel(x, this, "xtoy")

  def getAllX() -> List:
    rm.find_sources(this, "xtoy")

  def removeX(x) -> None:
    rm.remove_rel(x, this, "xtoy")
				

Implementing many to many relationships between class X and Y

#8.

* → *, directional, all methods on X

  Plural  API              No API
 _____________        ______________
|      X      |      |       Y      |
|_____________|      |______________|
|             |      |              |
|addY(y)      |*    *|              |
|getAllY()    | ⎯⎯⎯⎯⎯⎯→ |              |
|removeY(y)   |      |              |
|_____________|      |______________|
				

DRAFT API (TODO, not tested)

#9.

* → *, directional, all methods on Y

      No API            Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
|              |*    *|addX(x)       |
|              | ⎯⎯⎯⎯⎯⎯→ |getAllX()     |
|              |      |removeX(x)    |
|______________|      |______________|
				

DRAFT API (TODO, not tested)

#10.

* ←→ *, bi-directional, methods on both X and Y

   Plural  API          Plural  API
 ______________        ______________
|     X        |      |       Y      |
|______________|      |______________|
|              |      |              |
| addY(y)      |*    *| addX(x)      |
| getAllY()    | ←⎯⎯⎯⎯→ | getAllX()    |
| removeY(y)   |      | removeX(x)   |
|______________|      |______________|
				

DRAFT API (TODO, not tested)