How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Build a parent and child record hierarchy

A definition with a parent definition makes every record a child of one parent record: created from the parent, sharing its site, moving with it. Give the child its own record path, and walk getParent() and getChildren().

Context

Context. A test belongs to one enrolment and cannot exist without it. That is a typed parent and child: the test definition names enrolment as its parent definition, so every test record must have an enrolment as its parent, is created from the enrolment's admin screen (when the parent has exactly one child definition), inherits its site and moves with it. The other hierarchy, a same-type tree, is for records nesting under their own kind.

Steps

  1. Create the test definition with parent definition Enrolment, and give it its own record path (test). A child without one gets a nested URL the detail route cannot resolve.
  2. Add its layout areas (questions, pass mark, attempt date) to its layout.
  3. From the enrolment, read the tests with getChildren(); from a test, read the enrolment with getParent().
  4. Gate the detail templates by the owning member, since a child's URL is public like any other.

Template code

Template code
{* enrolments/show.html *}
{$tests = $enrolment->getChildren()}
<h2>Tests</h2>
{foreach $tests as $test}
  <li><a href="{$test->getFullPath()}">{$test->getName()|escape}</a> {if $test->passed}passed{/if}</li>
{foreachelse}
  <li>No tests yet.</li>
{/foreach}

{* tests/show.html *}
{$enrolment = $test->getParent()}
{if $enrolment->getAreaText('user') != $_currentUser->getId() && !$_currentUser->isAdmin()}{$_response->redirect('/my-account')}{/if}
<p>Part of <a href="{$enrolment->getFullPath()}">{$enrolment->getName()|escape}</a></p>
{# enrolments/show.html #}
{% set tests = enrolment.getChildren() %}
<h2>Tests</h2>
{% for test in tests %}
  <li><a href="{{ test.getFullPath() }}">{{ test.getName()|e }}</a> {% if test.passed %}passed{% endif %}</li>
{% else %}
  <li>No tests yet.</li>
{% endfor %}

{# tests/show.html #}
{% set enrolment = test.getParent() %}
{% if enrolment.getAreaText('user') != _currentUser.getId() and not _currentUser.isAdmin() %}{% do _response.redirect('/my-account') %}{% endif %}
<p>Part of <a href="{{ enrolment.getFullPath() }}">{{ enrolment.getName()|e }}</a></p>

Why this and not that

Not a relationship from test to enrolment: a relationship is for two things that stand alone, and a test without its enrolment is meaningless; the hierarchy also gives authors "create child" on the parent and cascades the site. Not a same-type tree for this: that is for a region containing sub-regions. A recursive render of a same-type tree needs a depth guard, because an author-built tree has no natural bottom.

Go deeper

Was this page helpful?

Your answer goes to the docs team.