Documenting this for others as needed. I asked directly how best to go about adding a Many to One table relationship for exportable objects for CTools integration. The answer follows:

---

In Drupal 7, CTools actually provides a special callback to handle
one::many (or parent::child) relationships using the export system. This
is called the 'subrecords callback'. In Drupal 7, Views is making use of
this. Views are stored in two tables: There's the core 'view' table,
which is actually very small, and then each 'display' has its own child
record.

Views defines the CTools export info like this:

  $schema['views_view'] = array(
    'description' => 'Stores the general data for a view.',
    'export' => array(
      'identifier' => 'view',
      'bulk export' => TRUE,
      'primary key' => 'vid',
      'default hook' => 'views_default_views',
      'admin_title' => 'human_name',
      'admin_description' => 'description',
      'api' => array(
        'owner' => 'views',
        'api' => 'views_default',
        'minimum_version' => '2',
        'current_version' => '3.0',
      ),
      'object' => 'view',
      // the callback to load the displays
      'subrecords callback' => 'views_load_display_records',
      // the variable that holds enabled/disabled status
      'status' => 'views_defaults',
      // CRUD callbacks
      'create callback' => 'views_new_view',
      'save callback' => 'views_save_view',
      'delete callback' => 'views_delete_view',
      'export callback' => 'views_export_view',
      'cache defaults' => TRUE,
      'default cache bin' => 'cache_views',
    ),

And this is the subrecords callback:

/**
 * Export callback to load the view subrecords, which are the displays.
 */
function views_load_display_records(&$views) {
  // Get vids from the views.
  $names = array();
  foreach ($views as $view) {
    if (empty($view->display)) {
      $names[$view->vid] = $view->name;
    }
  }

  if (empty($names)) {
    return;
  }

  foreach (view::db_objects() as $key) {
    $object_name = "views_$key";
    $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
      array(':vids' => array_keys($names)));

    foreach ($result as $data) {
      $object = new $object_name(FALSE);
      $object->load_row($data);

      // Because it can get complicated with this much indirection,
      // make a shortcut reference.
      $location = &$views[$names[$object->vid]]->$key;

      // If we have a basic id field, load the item onto the view based on
      // this ID, otherwise push it on.
      if (!empty($object->id)) {
        $location[$object->id] = $object;
      }
      else {
        $location[] = $object;
      }
    }
  }
}

Do note that there isn't a subrecords save callback specifically, so the
'save callback' has to be defined to take care of that, but it's pretty
straightforward. I won't show Views' save callback here because it goes
through a couple layers of abstraction that make it less effective as an
example, but it's really just a simple process of "first save the parent
record", "then save all the child records".

Note that export has to do something similar.

Comments

slucero’s picture

Thanks for this! I've been searching for documentation on this for a while now.

slucero’s picture

Issue summary: View changes

Added PHP tags to make code easier to read.

mustanggb’s picture

Status: Active » Closed (outdated)