public static function relate($options) {
        self::_normalizeOptionsForRelate($options);
        $modelA  = $options['modelA'];
        $modelB  = $options['modelB'];

        /**
         * If bindingModel is set, we can safely skip all those difficult checks below.
         */
        if (is_object($options['bindingModel'])) {
            return self::_relateHasAndBelongsToMany($options);
        }
        try {
            /**
             * If this succeeds, it's a regular relationship where the foreign key
             * resides inside modelA. Continue as usual.
             */
            $reference = $modelA->getReference(get_class($modelB), $options['rule']);
        } catch (Exception $e) {
            if (!self::isInvalidReferenceException($e)) {
                throw $e;
            }
            try {
                /**
                 * If this succeeds, the foreign key resides in the modelA.
                 * Flip modelA and modelB and keyA and keyB in order to normalize
                 * the given configuration.
                 * Call self::relate() recursively with these new options.
                 */
                $reference = $modelB->getReference(get_class($modelA), $options['rule']);

                $keyA = $options['keyA'];
                $keyB = $options['keyB'];
                $options['modelA'] = $modelB;
                $options['modelB'] = $modelA;
                $options['keyA'] = $keyB;
                $options['keyB'] = $keyA;
                return Garp_Content_Relation_Manager::relate($options);
            } catch (Exception $e) {
                if (!self::isInvalidReferenceException($e)) {
                    throw $e;
                }
                /**
                 * Goody, we're dealing with a hasAndBelongsToMany relationship here.
                 * Try to construct the intersection model and save the relation
                 * that way.
                 */
                return self::_relateHasAndBelongsToMany($options);
            }
        }

        $rowA = call_user_func_array(array($options['modelA'], 'find'), (array)$options['keyA']);
        if (!count($rowA)) {
            $errorMsg = sprintf(
                self::EXCEPTION_ROW_NOT_FOUND_BY_PRIMARY_KEY,
                $modelA->getName(), implode(',', (array)$options['keyA'])
            );
            throw new Garp_Content_Relation_Exception($errorMsg);
        }
        $rowA = $rowA->current();
        self::_addForeignKeysToRow($rowA, $reference, $options['keyB']);
        return $rowA->save();
    }