Browse Source

The Chado field storage is now inserting/updating all fields again using new field 'value' format

Stephen Ficklin 8 years ago
parent
commit
bb3ef3769b

+ 2 - 31
tripal/includes/TripalField.inc

@@ -2,7 +2,7 @@
 
 
 /**
- * A base class for all Fields supported by the Tripal Chado module.
+ * A base class for all Fields supported by Tripal.
  *
  * This class provides all of the necessary functions for a TripalField field.
  * It helps simplify and unify the process of creating fields for Tripal.  This
@@ -197,38 +197,9 @@ class TripalField {
   /**
    * Loads the field values from the underlying data store.
    *
-   * This function is called by the tripal_chado_field_storage_load() for
-   * each property managed by the field_chado_storage storage type.  This is
-   * an optional hook function that is only needed if the field has
-   * multiple form elements.
-   *
-   * This function must set the value for the field in the entity object. For
-   * example:
-   *
-   * @code
-   *   $field_name = $field['field_name'];
-   *   $field_type = $field['type'];
-   *   $language = 'und';
-   *   $delta = 0;
-   *   $entity->{$field_name}[$language][$delta]['value'] = TRUE;
-   * @endcode
-   *
-   * The field in the entity is an associative array where the first level is
-   * the field name, followed by the language.  The 'und' value indicates
-   * that the language is undefined and is the default.  Next is the 'delta'
-   * value. For field with a cardinality of 1, the delta value will always be
-   * 0.  For fields with a cardinality greater than 1 then the delta should
-   * increment for each value.  Next is a list of key/value pairs one of which
-   * should have the name 'value'.  The 'value' key should always contain the
-   * primary value that should be displayed to the user.  It can be a single
-   * value, or an array.  Any other number of keys can be present to help
-   * with the display. These keys also correspond to the names of the form
-   * fields specified by the widget() function of this class.
-   *
    * @param $field
    * @param $entity
-   * @param $base_table
-   * @param $record
+   * @param $details
    *
    * @return
    *   An array of the following format:

+ 9 - 0
tripal/includes/TripalFieldStorage.inc

@@ -0,0 +1,9 @@
+<?php
+
+
+/**
+ * A base class for all field storage mechanisms supported by Tripal.
+ *
+ */
+class TripalFieldStorage {
+}

+ 29 - 24
tripal_chado/includes/fields/chado_linker__dbxref.inc

@@ -113,18 +113,16 @@ class chado_linker__dbxref extends TripalField {
 
     $chado_table = $field['settings']['chado_table'];
     foreach ($items as $delta => $item) {
-      $accession = '';
-      if ($item[$chado_table . '__dbxref_id']) {
-        $dbxref = chado_generate_var('dbxref', array('dbxref_id' => $item[$chado_table . '__dbxref_id']));
-        $accession = $dbxref->db_id->name . ':' . $dbxref->accession;
-        if ($dbxref->db_id->urlprefix) {
-          $accession = l($accession, $dbxref->db_id->urlprefix . '/' . $dbxref->accession, array('attributes' => array('target' => '_blank')));
+      if ($item['value']) {
+        $content = $item['value']['namespace'] . ':' . $item['value']['accession'];
+        if ($item['value']['URL']) {
+          $content = l($item['value']['URL'], $item['value']['URL']);
         }
+        $element[$delta] = array(
+          '#type' => 'markup',
+          '#markup' => $content,
+        );
       }
-      $element[$delta] = array(
-        '#type' => 'markup',
-        '#markup' => $accession,
-      );
     }
   }
 
@@ -363,6 +361,11 @@ function chado_linker__dbxref_widget_validate($element, &$form_state) {
   $delta = $element['#delta'];
   $table_name = $element['#table_name'];
   $fkey = $element['#fkey_field'];
+  $field = field_info_field($field_name);
+  $field_type = $field['type'];
+  $field_table = $field['settings']['chado_table'];
+  $field_column = $field['settings']['chado_column'];
+  $field_prefix = $field_table . '__dbxref_id';
 
   // If the form ID is field_ui_field_edit_form, then the user is editing the
   // field's values in the manage fields form of Drupal.  We don't want
@@ -372,11 +375,11 @@ function chado_linker__dbxref_widget_validate($element, &$form_state) {
   }
 
   // Get the field values.
-  $dbxref_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_name . '__dbxref_id');
-  $db_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_name . '--dbxref__db_id');
-  $accession = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_name . '--dbxref__accession');
-  $version = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_name . '--dbxref__version');
-  $description = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_name . '--dbxref__description');
+  $dbxref_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_table . '__dbxref_id');
+  $db_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_prefix . '--db_id');
+  $accession = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_prefix . '--accession');
+  $version = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_prefix . '--version');
+  $description = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_prefix . '--description');
 
   // Make sure that if a database ID is provided that an accession is also
   // provided.  Here we use the form_set_error function rather than the
@@ -386,26 +389,23 @@ function chado_linker__dbxref_widget_validate($element, &$form_state) {
   // we borrow the code from the 'form_error' function and append the field
   // so that the proper field is highlighted on error.
   if (!$db_id and $accession) {
-    form_set_error(implode('][', $element ['#parents']) . '][' . $table_name . '--dbxref__db_id', t("A database and the accession must both be provided."));
+    form_set_error(implode('][', $element ['#parents']) . '][' . $field_prefix . '--db_id', t("A database and the accession must both be provided."));
   }
   if ($db_id and !$accession) {
-    form_set_error(implode('][', $element ['#parents']) . '][' . $table_name . '--dbxref__accession', t("A database and the accession must both be provided."));
+    form_set_error(implode('][', $element ['#parents']) . '][' . $field_prefix . '--accession', t("A database and the accession must both be provided."));
   }
   if (!$db_id and !$accession and ($version or $description)) {
-    form_set_error(implode('][', $element ['#parents']) . '][' . $table_name . '--dbxref__db_id', t("A database and the accession must both be provided."));
+    form_set_error(implode('][', $element ['#parents']) . '][' . $field_prefix . '--db_id', t("A database and the accession must both be provided."));
   }
 
   // If the dbxref_id does not match the db_id + accession then the user
   // has selected a new dbxref record and we need to update the hidden
   // value accordingly.
   if ($db_id and $accession) {
-    $fkey_value = $element['#entity']->chado_record_id;
-    tripal_chado_set_field_form_values($field_name, $form_state, $fkey_value, $delta, $table_name . '__' . $fkey);
-
     $dbxref = chado_generate_var('dbxref', array('db_id' => $db_id, 'accession' => $accession));
     if ($dbxref and $dbxref->dbxref_id != $dbxref_id) {
       tripal_chado_set_field_form_values($field_name, $form_state, $dbxref->dbxref_id, $delta, $table_name . '__dbxref_id');
-      tripal_chado_set_field_form_values($field_name, $form_state, $dbxref->dbxref_id, $delta, $table_name . '--dbxref__dbxref_id');
+      tripal_chado_set_field_form_values($field_name, $form_state, $dbxref->dbxref_id, $delta, $field_prefix . '--dbxref_id');
     }
   }
   else {
@@ -421,11 +421,16 @@ function chado_linker__dbxref_widget_form_ajax_callback($form, $form_state) {
 
   $field_name = $form_state['triggering_element']['#parents'][0];
   $delta = $form_state['triggering_element']['#parents'][2];
+  $field = field_info_field($field_name);
+  $field_type = $field['type'];
+  $field_table = $field['settings']['chado_table'];
+  $field_column = $field['settings']['chado_column'];
+  $field_prefix = $field_table . '__dbxref_id';
 
   // Check to see if this dbxref already exists. If not then
   // give a notice to the user that the dbxref will be added.
-  $db_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_name . '--dbxref__db_id');
-  $accession = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_name . '--dbxref__accession');
+  $db_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_prefix . '--db_id');
+  $accession = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_prefix . '--accession');
   if ($db_id and $accession) {
     $values = array(
       'db_id' => $db_id,

+ 150 - 7
tripal_chado/includes/fields/chado_linker__relationship.inc

@@ -93,7 +93,6 @@ class chado_linker__relationship extends TripalField {
    */
   function formatter_view(&$element, $entity_type, $entity,
       $field, $instance, $langcode, $items, $display) {
-
     // Get the settings
     $settings = $display['settings'];
 
@@ -151,7 +150,7 @@ class chado_linker__relationship extends TripalField {
       'header' => $headers,
       'rows' => $rows,
       'attributes' => array(
-        'id' => 'tripal_feature-table-alignments',
+        'id' => 'chado-linker--relationship-table',
         'class' => 'tripal-data-table'
       ),
       'sticky' => FALSE,
@@ -167,6 +166,96 @@ class chado_linker__relationship extends TripalField {
       '#markup' => theme_table($table),
     );
   }
+  /**
+   * @see TripalField::widget_form()
+   */
+  function widget_form(&$widget, $form, $form_state, $field,
+      $instance, $langcode, $items, $delta, $element) {
+
+    $entity = $form['#entity'];
+    $field_name = $field['field_name'];
+
+    // Get the FK column that links to the base table.
+    $table_name = $field['settings']['chado_table'];
+    $base_table = $field['settings']['base_table'];
+    $schema = chado_get_schema($table_name);
+    $pkey = $schema['primary key'][0];
+    $fkeys = array_values($schema['foreign keys'][$base_table]['columns']);
+    $fkey = $fkeys[0];
+
+    // Get the field defaults.
+    $record_id = '';
+    $fkey_value = $element['#entity']->chado_record_id;
+    $subject_id = '';
+    $type_id = '';
+    $object_id = '';
+    $value = '';
+    $rank = '';
+
+    // If the field already has a value then it will come through the $items
+    // array.  This happens when editing an existing record.
+    if (array_key_exists($delta, $items)) {
+      $record_id = $items[$delta][$table_name . '__' . $pkey];
+      $subject_id = $items[$delta][$table_name . '__subject_id'];
+      $type_id = $items[$delta][$table_name . '__type_id'];
+      $object_id = $items[$delta][$table_name . '__object_id'];
+      $value = $items[$delta][$table_name . '__value'];
+      $rank = $items[$delta][$table_name . '__rank'];
+    }
+
+    // Check $form_state['values'] to see if an AJAX call set the values.
+    if (array_key_exists('values', $form_state) and array_key_exists($delta, $form_state['values'])) {
+      $record_id = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__' . $pkey);
+      $subject_id = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__subject_id');
+      $type_id = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__type_id');
+      $object_id = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__object_id');
+      $value = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__value');
+      $rank = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__rank');
+    }
+
+    $widget['#table_name'] = $table_name;
+    $widget['#fkey_field'] = $fkey;
+//    $widget['#element_validate'] = array('chado_linker__relationship_validate');
+//    $widget['#theme'] = 'chado_linker__relationship_widget';
+    $widget['#prefix'] =  "<span id='$table_name-$delta'>";
+    $widget['#suffix'] =  "</span>";
+
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+
+    $widget[$table_name . '__' . $pkey] = array(
+      '#type' => 'value',
+      '#default_value' => $record_id,
+    );
+    // TODO: for now just add all fields as values, eventually we need
+    // a form for specifying relationships.
+    $widget[$table_name . '__subject_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $subject_id,
+    );
+    $widget[$table_name . '__type_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $type_id,
+    );
+    $widget[$table_name . '__object_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $object_id,
+    );
+    if (array_key_exists('value', $schema['fields'])) {
+      $widget[$table_name . '__value'] = array(
+        '#type' => 'value',
+        '#default_value' => $value,
+      );
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $widget[$table_name . '__rank'] = array(
+        '#type' => 'value',
+        '#default_value' => $rank,
+      );
+    }
+  }
 
   /**
    * @see TripalField::load()
@@ -182,10 +271,48 @@ class chado_linker__relationship extends TripalField {
     $field_column = $field['settings']['chado_column'];
     $base_table = $field['settings']['base_table'];
 
+    // Get the PKey for this table
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+
+    // Get the Pkeys for the subject and object tables
+    $subject_fkey_table = '';
+    $object_fkey_table = '';
+    $fkeys = $schema['foreign keys'];
+    foreach ($fkeys as $fktable => $details) {
+      foreach ($details['columns'] as $fkey_lcolumn => $fkey_rcolumn) {
+        if ($fkey_lcolumn == 'subject_id') {
+          $subject_fkey_table = $fktable;
+        }
+        if ($fkey_lcolumn == 'object_id') {
+          $object_fkey_table = $fktable;
+        }
+      }
+    }
+    $subject_schema = chado_get_schema($subject_fkey_table);
+    $object_schema = chado_get_schema($object_fkey_table);
+    $subject_pkey = $subject_schema['primary key'][0];
+    $object_pkey = $object_schema['primary key'][0];
+
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
       'value' => array(),
+      $field_table . '__' . $pkey => '',
+      $field_table . '__subject_id' => '',
+      $field_table . '__object_id' => '',
+      $field_table . '__type_id' => TRUE,
     );
+    if (array_key_exists('value', $schema['fields'])) {
+      $entity->{$field_name}['und'][0][$field_table . '__value'] = '';
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $entity->{$field_name}['und'][0][$field_table . '__rank'] = '';
+    }
 
     if (!$record) {
       return;
@@ -195,17 +322,14 @@ class chado_linker__relationship extends TripalField {
     $options = array(
       'return_array' => 1,
       // we don't want to fully recurse we only need information about the
-      // relationship type and the object and subject  (including feature type
-      // and organism)
+      // relationship type and the object and subject
       'include_fk' => array(
         'type_id' => 1,
         'object_id' => array(
           'type_id' => 1,
-          'organism_id' => 1
         ),
         'subject_id'  => array(
           'type_id' => 1,
-          'organism_id' => 1
         ),
       ),
     );
@@ -267,7 +391,16 @@ class chado_linker__relationship extends TripalField {
         $entity->{$field_name}['und'][$i]['value']['phrase'] = 'The ' . $subject_type . ', ' .
           $subject_name . ', ' . $verb . ' '  . $rel_type_clean . ' this '  .
           $object_type . '.';
-
+        $entity->{$field_name}['und'][$i][$field_table . '__' . $pkey] = $relationship->$pkey;
+        $entity->{$field_name}['und'][$i][$field_table . '__subject_id'] = $relationship->subject_id->$subject_pkey;
+        $entity->{$field_name}['und'][$i][$field_table . '__type_id'] = $relationship->type_id->cvterm_id;
+        $entity->{$field_name}['und'][$i][$field_table . '__object_id'] = $relationship->object_id->$object_pkey;
+        if (array_key_exists('value', $schema['fields'])) {
+          $entity->{$field_name}['und'][$i][$field_table . '__value'] = $relationship->value;
+        }
+        if (array_key_exists('rank', $schema['fields'])) {
+          $entity->{$field_name}['und'][$i][$field_table . '__rank'] = $relationship->rank;
+        }
         $i++;
       }
     }
@@ -316,6 +449,16 @@ class chado_linker__relationship extends TripalField {
         $entity->{$field_name}['und'][$i]['value']['phrase'] = 'This  ' .
           $subject_type . ' ' . $verb . ' '  . $rel_type_clean . ' the '  .
           $object_type . ', ' . $object_name . '.';
+        $entity->{$field_name}['und'][$i][$field_table . '__' . $pkey] = $relationship->$pkey;
+        $entity->{$field_name}['und'][$i][$field_table . '__subject_id'] = $relationship->subject_id->$subject_pkey;
+        $entity->{$field_name}['und'][$i][$field_table . '__type_id'] = $relationship->type_id->cvterm_id;
+        $entity->{$field_name}['und'][$i][$field_table . '__object_id'] = $relationship->object_id->$object_pkey;
+        if (array_key_exists('value', $schema['fields'])) {
+          $entity->{$field_name}['und'][$i][$field_table . '__value'] = $relationship->value;
+        }
+        if (array_key_exists('rank', $schema['fields'])) {
+          $entity->{$field_name}['und'][$i][$field_table . '__rank'] = $relationship->rank;
+        }
         $i++;
       }
     }

+ 17 - 14
tripal_chado/includes/fields/chado_linker__synonym.inc

@@ -142,8 +142,8 @@ class chado_linker__synonym extends TripalField {
       $pub_id = $items[$delta][$table_name . '__pub_id'];
       $is_current = $items[$delta][$table_name . '__is_current'];
       $is_internal = $items[$delta][$table_name . '__is_internal'];
-      $syn_name = $items[$delta][$table_name . '--synonym__name'];
-      $syn_type = $items[$delta][$table_name . '--synonym__type_id'];
+      $syn_name = $items[$delta][$table_name . '__synonym_id--name'];
+      $syn_type = $items[$delta][$table_name . '__synonym_id--type_id'];
     }
 
     // Check $form_state['values'] to see if an AJAX call set the values.
@@ -154,8 +154,8 @@ class chado_linker__synonym extends TripalField {
       $pub_id = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__pub_id');
       $is_current = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__is_current');
       $is_internal = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__is_internal');
-      $syn_name = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '--synonym__name');
-      $syn_type = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '--synonym__type_id');
+      $syn_name = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__synonym_id--name');
+      $syn_type = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__synonym_id--type_id');
     }
 
     // Get the synonym type terms.  There shouldn't be too many.
@@ -186,14 +186,18 @@ class chado_linker__synonym extends TripalField {
       '#type' => 'value',
       '#default_value' => $fkey_value,
     );
-
-    $widget[$table_name . '--synonym__type_id'] = array(
+    // TODO: add a widget for selecting a publication.
+    $widget[$table_name . '__pub_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $pub_id,
+    );
+    $widget[$table_name . '__synonym_id--type_id'] = array(
       '#type' => 'select',
       '#title' => t('Type'),
       '#options' => $options,
       '#default_value' => $syn_type,
     );
-    $widget[$table_name . '--synonym__name'] = array(
+    $widget[$table_name . '__synonym_id--name'] = array(
       '#type' => 'textfield',
       '#title' => t('Synonym Name'),
       '#default_value' => $syn_name,
@@ -213,7 +217,6 @@ class chado_linker__synonym extends TripalField {
       '#default_value' => $is_internal,
       '#required' => $element['#required'],
     );
-
   }
 
   /**
@@ -247,8 +250,8 @@ class chado_linker__synonym extends TripalField {
       $field_table . '__' . 'pub_id' => '',
       $field_table . '__' . 'is_current' => TRUE,
       $field_table . '__' . 'is_internal' => '',
-      $field_table . '--' . 'synonym__name' => '',
-      $field_table . '--' . 'synonym__type_id' => '',
+      $field_table . '__synonym_id--name' => '',
+      $field_table . '__synonym_id--type_id' => '',
       // Ignore the synonym_sgml column for now.
     );
 
@@ -271,8 +274,8 @@ class chado_linker__synonym extends TripalField {
           $field_table . '__' . 'pub_id' => $linker->pub_id->pub_id,
           $field_table . '__' . 'is_current' => $linker->is_current,
           $field_table . '__' . 'is_internal' => $linker->is_internal,
-          $field_table . '--' . 'synonym__name' => $synonym->name,
-          $field_table . '--' . 'synonym__type_id' => $synonym->type_id->cvterm_id,
+          $field_table . '__synonym_id--name' => $synonym->name,
+          $field_table . '__synonym_id--type_id' => $synonym->type_id->cvterm_id,
         );
         $i++;
       }
@@ -296,10 +299,10 @@ function theme_chado_linker__synonym_widget($variables) {
   $layout = "
       <div class=\"synonym-widget\">
         <div class=\"synonym-widget-item\">" .
-        drupal_render($element[$table_name . '--synonym__name']) . "
+        drupal_render($element[$table_name . '__synonym_id--name']) . "
         </div>
         <div>" .
-        drupal_render($element[$table_name . '--synonym__type_id']) . "
+        drupal_render($element[$table_name . '__synonym_id--type_id']) . "
         </div>
         <div class=\"synonym-widget-item\">" .
         drupal_render($element[$table_name . '__is_internal']) . "

+ 115 - 191
tripal_chado/includes/tripal_chado.field_storage.inc

@@ -33,22 +33,19 @@ function tripal_chado_field_storage_write($entity_type, $entity, $op, $fields) {
   $type_field = $entity->chado_column;
   $record     = $entity->chado_record;
   $record_id  = $entity->chado_record_id;
+  $base_schema = chado_get_schema($base_table);
+  $base_pkey = $base_schema['primary key'][0];
 
   // Convert the fields into a key/value list of fields and their values.
   $field_vals = tripal_chado_field_storage_merge_fields($fields, $entity_type, $entity);
-dpm($field_vals);
-return;
-  // Write the record for the base table.
-  $record_id = tripal_chado_field_storage_write_table(array(
-    'entity' => $entity,
-    'term' => $term,
-    'op' => $op,
-    'field_vals' => $field_vals,
-    'base_table' => $base_table,
-    'tablename' => $base_table,
-    'type_field' => $type_field,
-    'record_id' => $record_id,
-  ));
+
+  // Write the record for the base table.  If this is an update then we'll have
+  // the record_id and we need to add that to our values array.
+  $values = $field_vals[$base_table][0];
+  if ($record_id) {
+    $values[$base_pkey] = $record_id;
+  }
+  $base_record_id = tripal_chado_field_storage_write_table($base_table, $values);
 
   // If this is an insert then add the chado_entity record.
   if ($op == FIELD_STORAGE_INSERT) {
@@ -68,185 +65,108 @@ return;
   }
 
   // Now that we have handled the base table, we need to handle linking tables.
-  foreach ($fields as $field_id) {
-    // Get the field using the id.
-    $field = field_info_field_by_id($field_id);
-    $field_name = $field['field_name'];
-
-    // If the field has a chado_table setting then we can try to write.
-    if (array_key_exists('settings', $field) and array_key_exists('chado_table', $field['settings'])) {
-
-      // Skip fields that use the base table, as we've already handled those.
-      if ($field['settings']['chado_table'] != $base_table){
-        $field_table = $field['settings']['chado_table'];
-
-        // Iterate through each record.
-        if (array_key_exists($field_name, $field_vals)) {
-          foreach ($field_vals[$field_name] as $delta => $fvals) {
-            tripal_chado_field_storage_write_table(array(
-              'entity' => $entity,
-              'term' => $term,
-              'op' => $op,
-              'field_vals' => $fvals,
-              'base_table' => $base_table,
-              'tablename' => $field_table
-            ));
-          }
-        }
-      }
+  foreach ($field_vals as $table_name => $details) {
+    // Skip the base table as we've already dealt with it.
+    if ($table_name == $base_table) {
+      continue;
+    }
+    foreach ($details as $delta => $values) {
+      $record_id = tripal_chado_field_storage_write_table($table_name, $values);
     }
   }
 }
 
 /**
+ * Write (inserts/oupdates) a nested array of values for a table.
+ *
+ * The $values array is of the same format used by chado_insert_record() and
+ * chado_update_record().  However, both of those methods will use any nested
+ * arrays (i.e. representing foreign keys) to select an appropriate record ID
+ * that can be substituted as the value.  Here, the nested arrays are
+ * either inserted or updated as well, but the choice is determined if the
+ * primary key value is present.  If present an update occurs, if not present
+ * then an insert occurs.
  *
+ * This function is recursive and nested arrays from the lowest point of the
+ * "tree" are dealt with first.
+ *
+ * @param $table_name
+ *   The name of the table on which the insertion/update is performed.
+ * @param $values
+ *   The values array for the insertion.
+ * @throws Exception
+ * @return
+ *   The unique record ID.
  */
-function tripal_chado_field_storage_write_table($params) {
-  $entity = $params['entity'];
-  $term = $params['term'];
-  $op = $params['op'];
-  $field_vals = $params['field_vals'];
-  $base_table = $params['base_table'];
-  $tablename = $params['tablename'];
-  $type_field = array_key_exists('type_field', $params) ? $params['type_field'] : NULL;
-  $record_id = array_key_exists('record_id', $params) ? $params['record_id'] : NULL;
-  $depth = array_key_exists('depth', $params) ? $params['depth'] : 0;
-
-  // Intialize the values array.
-  $values = array();
-
-  // Get the schema for this table so that we can identify the primary key
-  // and foreign keys.
-  $schema = chado_get_schema($tablename);
-  $pkey_field = $schema['primary key'][0];
-  $fkey_fields = $schema['foreign keys'];
-  $fkey_fields_list = array();
-  $fkey_base_linker = NULL;
-
-  // STEP 1: Recurse on the FK fields.
-  // Loop through the foreign keys so that we can recurse on those first.
-  foreach ($fkey_fields as $fk_table => $details) {
-    foreach ($details['columns'] as $local_id => $remote_id) {
-
-      // If this is the base table then do not recurse on the type_id.
-      if ($tablename == $base_table && $local_id == $type_field) {
-        $values[$local_id] = $term->details['cvterm']->cvterm_id;
-        continue;
-      }
-
-      // If this is a linking table, do not recurse on the fields that
-      // link back to the base table.
-      if ($tablename != $base_table && $details['table'] == $base_table) {
-        $fkey_base_linker = $local_id;
-        continue;
-      }
-
-      // Get the value of the FK field as provided by the user.
-      $fk_val = NULL;
-      $fk_vals = array();
-      $fk_field_name = $tablename . '__' . $local_id;
-      if (array_key_exists($fk_field_name, $field_vals)) {
-        $fk_val = $field_vals[$fk_field_name][0][$tablename . '__' . $local_id];
-        $fk_vals = $field_vals[$fk_field_name][0];
-      }
-
-      // Don't recurse if the value of the FK field is set to NULL.  The
-      // Tripal Chado API value for NULL is '__NULL__'.
-      if ($fk_val == "__NULL__") {
-        $values[$local_id] = $fk_val;
-        continue;
-      }
-
-      // Don't recuse if there are no fkvals.
-      if (count(array_keys($fk_vals)) == 0) {
-        continue;
-      }
-
-      // Keep track of the FK fields so that in STEP 2 we don't have to
-      // loop through the $fk_fields again.
-      $fkey_fields_list[] = $local_id;
-
-      // Recurse on the FK field.
-      $nparams = $params;
-      $nparams['field_vals'] = $fk_vals;
-      $nparams['tablename'] = $fk_table;
-      $nparams['type_field'] = NULL;
-      $nparams['record_id'] = $fk_val;
-      $nparams['depth'] = $depth + 1;
-      $fk_val = tripal_chado_field_storage_write_table($nparams);
-      if (isset($fk_val) and $fk_val != '' and $fk_val != 0) {
-        $values[$local_id] = $fk_val;
-      }
-    }
-  }
-
-  // STEP 2: Loop through the non FK fields.
-  // Loop through the fields passed to the function and find any that
-  // are for this table.  Then add their values to the $values array.
-  foreach ($field_vals as $field_name => $items) {
-    if (preg_match('/^' . $tablename . '__(.*)/', $field_name, $matches)) {
-      $chado_field = $matches[1];
-
-      // Skip the PKey field. We won't ever insert a primary key and if
-      // one is provided in the fields then we use it for matching on an
-      // update.  We don't add it to the $values array in either case.
-      if ($chado_field == $pkey_field) {
-        continue;
-      }
-
-      // Skip FK fields as those should already have been dealt with the
-      // recursive code above.
-      if (in_array($chado_field, $fkey_fields_list)) {
-        continue;
-      }
-
-      // If the value is empty then exclude this field
-      if (!$items[0][$tablename . '__' . $chado_field]) {
-        continue;
-      }
-
-      // Add the value of the field to the $values arr for later insert/update.
-      $values[$chado_field] = $items[0][$tablename . '__' . $chado_field];
-    }
-  }
-
-  // STEP 3: Insert/Update the record.
-  // If there are no values then return.
-  if (count($values) == 0) {
-    return $record_id;
-  }
-
-  dpm($values);
-  // If we don't have an incoming record ID then this is an insert.
-  if ($record_id == NULL) {
-    // STEP 3a: Before inserting, we want to make sure the record does not
-    // already exist.  Using the unique constraint check for a matching record.
-    $options = array('is_duplicate' => TRUE);
-    $is_duplicate = chado_select_record($tablename, array('*'), $values, $options);
-    if($is_duplicate) {
-      $record = chado_select_record($tablename, array('*'), $values);
-      return $record[0]->$pkey_field;
-    }
+function tripal_chado_field_storage_write_table($table_name, $values) {
+   $schema = chado_get_schema($table_name);
+   $fkeys = $schema['foreign keys'];
+   $pkey = $schema['primary key'][0];
+
+   // Before inserting or updating this table, recruse if there are any
+   // nested FK array values.
+   foreach ($values as $column => $value) {
+     // If this value is an array then it must be a FK... let's recurse.
+     if (is_array($value)) {
+
+       // Find the name of the FK table for this column.
+       $fktable_name = '';
+       foreach ($fkeys as $fktable => $details) {
+         foreach ($details['columns'] as $fkey_lcolumn => $fkey_rcolumn) {
+           if ($fkey_lcolumn == $column) {
+             $fktable_name = $fktable;
+           }
+         }
+       }
+
+       // Recurse on this recod.
+       $record_id = tripal_chado_field_storage_write_table($fktable_name, $values[$column]);
+       $values[$column] = $record_id;
+     }
+   }
 
-    // STEP 3b: Insert the reocrd
-    // Insert the values array as a new record in the table.
-    $record = chado_insert_record($tablename, $values);
-    if ($record === FALSE) {
-      throw new Exception('Could not insert Chado record into table: "' . $tablename . '".');
-    }
-    $record_id = $record[$pkey_field];
-  }
-  // We have an incoming record_id so this is an update.
-  else {
-    // TODO: what if the unique constraint matches another record?  That is
-    // not being tested for here.
-    $match[$pkey_field] = $record_id;
-    if (!chado_update_record($tablename, $match, $values)) {
-      drupal_set_message("Could not update Chado record in table: $tablename.", 'error');
-    }
-  }
+   // Fields with a cardinality greater than 1 will often submit an
+   // empty form.  We want to remove these empty submissions.  We can detect
+   // them if all of the fields are empty.
+   $num_empty = 0;
+   foreach ($values as $column => $value) {
+     if (!$value) {
+       $num_empty++;
+     }
+   }
+   if ($num_empty == count(array_keys($values))) {
+     return '';
+   }
 
-  return $record_id;
+   // If the primary key column has a value then this will be an udpate,
+   // otherwise it's an insert.
+   if (!$values[$pkey]) {
+     // Before inserting, we want to make sure the record does not
+     // already exist.  Using the unique constraint check for a matching record.
+     $options = array('is_duplicate' => TRUE);
+     $is_duplicate = chado_select_record($table_name, array('*'), $values, $options);
+     if($is_duplicate) {
+       $record = chado_select_record($table_name, array('*'), $values);
+       return $record[0]->$pkey_field;
+     }
+
+     // Insert the values array as a new record in the table.
+     $record = chado_insert_record($table_name, $values);
+     if ($record === FALSE) {
+       throw new Exception('Could not insert Chado record into table: "' . $tablename . '".');
+     }
+     return $record_id = $record[$pkey_field];
+   }
+   // We have an incoming record_id so this is an update.
+   else {
+     // TODO: what if the unique constraint matches another record?  That is
+     // not being tested for here.
+     $match[$pkey] = $values[$pkey];
+     if (!chado_update_record($table_name, $match, $values)) {
+       drupal_set_message("Could not update Chado record in table: $tablename.", 'error');
+     }
+     return $values[$pkey];
+   }
 }
 
 /**
@@ -420,15 +340,19 @@ function tripal_chado_field_storage_merge_fields($fields, $entity_type, $entity)
             $parent_item_name = $matches[1];
             $sub_item_name = $matches[2];
             $sub_item = tripal_chado_field_storage_expand_field($sub_item_name, $value);
-            // If we've already encountered this table and column then we've
-            // already seen the numeric FK value or we've already added a
-            // subcolumn. If the former we want to convert this to an array
-            // so we can add the details.
-            if (array_key_exists($parent_item_name, $new_fields[$table_name][$delta]) and
-                !is_array($new_fields[$table_name][$delta][$parent_item_name])) {
-              $new_fields[$table_name][$delta][$parent_item_name] = array();
+            if (count(array_keys($sub_item))) {
+              // If we've already encountered this table and column then we've
+              // already seen the numeric FK value or we've already added a
+              // subcolumn. If the former we want to convert this to an array
+              // so we can add the details.
+              if (!array_key_exists($table_name, $new_fields) or
+                  !array_key_exists($delta, $new_fields[$table_name]) or
+                  !array_key_exists($parent_item_name, $new_fields[$table_name][$delta]) or
+                  !is_array($new_fields[$table_name][$delta][$parent_item_name])) {
+                $new_fields[$table_name][$delta][$parent_item_name] = array();
+              }
+              $new_fields[$table_name][$delta][$parent_item_name] += $sub_item;
             }
-            $new_fields[$table_name][$delta][$parent_item_name] += $sub_item;
           }
           else {
             // If not seen this table and column then just add it. If we've