HABTM с self требует в 2 раза больше строк в таблице соединений?

Я пытаюсь создать CMS с Nodes в качестве основной модели. Каждый Node belongsTo есть NodeType, и каждый Node может быть связан с любым/каждым другим Node.

Итак, подумал, что это требует HABTM:

//Node model
public $hasAndBelongsToMany = array(
    'AssociatedNode' => array(
        'className' => 'Node',
        'foreignKey' => 'node_id',
        'associationForeignKey' => 'associated_node_id',
        'joinTable' => 'node_associations'
    )
);

Проблема в том, что кажется, что это работает только в том случае, если у меня есть ДВЕ строки для каждой ассоциации.

Пример только с одной строкой ассоциации:

Узлы

  • ER (id=1)
  • Джордж Клуни (id=2)

Одна строка в таблице соединений, описывающая отношения между этими двумя узлами:

  • 'node_id' = 1
  • 'associated_node_id' = 2

Теперь - если я запрошу телешоу и содержу его узлы Актера:

$nodes = $this->Node->find('all', array(
        'conditions' => array(
            'Node.node_type_id' => '645' //tv shows
        ),
        'contain' => array(
            'AssociatedNode' => array(
                'conditions' => array(
                    'AssociatedNode.node_type_id' => '239' //actors
                ),
            )
        )
    ));

Это работает, и я получаю ER -> Джордж Клуни.

Но что, если я хочу отменить все шоу, в которых участвует Джордж Клуни?

$nodes = $this->Node->find('all', array(
        'conditions' => array(
            'Node.node_type_id' => '239' //actors
        ),
        'contain' => array(
            'AssociatedNode' => array(
                'conditions' => array(
                    'AssociatedNode.node_type_id' => '645' //tv shows
                ),
            )
        )
    ));

Это не работает, потому что он ищет идентификатор Джорджа Клуни в поле «node_id», а идентификатор ER — в поле «associated_node_id», хотя на самом деле они перевернуты.

Единственное решение, о котором я подумал, - это сохранить две строки для КАЖДОЙ ассоциации. Но это кажется излишеством. Но тогда мне нужно придумать что-то нестандартное, что гарантирует синхронизацию каждого дубликата с другим каждый раз, когда ассоциация сохраняется или удаляется... и т. д. - и это похоже на большую банку червей.

Есть что-то, что мне не хватает?


person Dave    schedule 01.09.2012    source источник
comment
звучит как поведение дерева для меня :D   -  person Kishor Kundan    schedule 20.09.2012


Ответы (3)


Вероятно, вы могли бы сделать это с помощью специального запроса, но, чтобы сохранить стандартные функции Cake, я могу придумать одну вещь: объявить два отношения между узлами:

public $hasAndBelongsToMany = array(
  'AssociatedNode1' => array(
      'className' => 'Node',
      'foreignKey' => 'node_id',
      'associationForeignKey' => 'associated_node_id',
      'joinTable' => 'node_associations'
  ),
  'AssociatedNode2' => array(
      'className' => 'Node',
      'foreignKey' => 'associated_node_id',
      'associationForeignKey' => 'node_id',
      'joinTable' => 'node_associations'
  )
);

а затем вы можете объединить оба массива в обратном вызове afterFind.

function afterFind($results)
{
  foreach($results as &$result)
  {
    if(isset($result['AssociatedNode1']) || isset($result['AssociatedNode2']))
    {
      $associated_nodes = array();

      if(isset($result['AssociatedNode1']))
      {
        foreach($result['AssociatedNode1'] as $associated_node)
        {
          $associated_nodes[] = $associated_node;
        }
      }

      if(isset($result['AssociatedNode2']))
      {
        foreach($result['AssociatedNode2'] as $associated_node)
        {
          $associated_nodes[] = $associated_node;
        }
      }

      $result['AssociatedNode'] = $associated_nodes;
    }
  }
  unset($result);

  return $results;
}

Но это заставит вас объявить как AssociatedNode1, так и AssociatedNode2 в вызове contains();

person nIcO    schedule 17.09.2012

Я не уверен, каковы детали вашего варианта использования, но у меня есть для вас несколько альтернативных вариантов:

Вы можете изучить использование Tree Behavior — это построенный для хранения вещей на деревьях, что, похоже, может быть тем, чем вы занимаетесь. Я не использовал его сам, поэтому я не уверен, насколько он применим к вашему использованию.

С другой стороны, если вы храните отношения в постоянном направлении (т. е. всегда телешоу-> актер) и знаете, в каком направлении выполняются ваши запросы (просматривая дерево для телешоу, в котором находится актер, по сравнению с просмотром вниз для поиска актеров в телешоу), вы должны иметь возможность запрашивать AssociatedNode, когда вы идете в обратном направлении, например

$nodes = $this->AssociatedNode->find('all', array(
    'conditions' => array(
        'AssociatedNode.node_type_id' => '239' //actors
    ),
    'contain' => array(
        'Node' => array(
            'conditions' => array(
                'Node.node_type_id' => '645' //tv shows
            ),
        )
    )
));

В этом случае для ясности лучше использовать «ChildNode» вместо «AssociatedNode».

Но опять же, оба эти ответа зависят от особенностей вашего варианта использования - ответ nIcO - хорошее общее решение. Это (обязательно) неудобно и, возможно, медленнее, но прекрасно абстрагирует неловкость.

person cincodenada    schedule 19.09.2012

Одна вещь, которую я сделал в прошлом, и которая может помочь, — это создание модели для таблицы соединения. Я мог хранить там дополнительные данные и делать со своими запросами все, что хотел. Затем на обеих сторонах этой модели соединения просто определите ассоциацию hasMany (может быть, и createdTo). Затем вы можете выполнить поиск, используя модель соединения, и написать что-то вроде (из контроллера):

$this->Node->NodesNode->find('all', array('conditions'=>array("or"=>array('node_id'=>$id,'sub_node_id'=>$id))));

ИМХО: на самом деле ничто не заставляет вас использовать соглашения о тортах. Я люблю торт, но иногда и он, и ORM усложняют очень простые вещи. Вы можете просто написать свой собственный запрос и самостоятельно проанализировать результаты. Вероятно, это будет быстрее, чем иметь дело с накладными расходами другого поведения или модели, плюс вы, вероятно, могли бы написать более лучший запрос, чем заданные по умолчанию.

Да, и, наконец, я бы остерегался, когда вы используете 1 модель для нескольких целей. Действительно подумайте, действительно ли эта модель должна поддерживать все. Я обнаружил, что всякий раз, когда я это делал, я просто переписывал все целиком в течение года или двух. Вы быстро столкнетесь с узким местом, где некоторым узлам нужно дополнительное поведение таким образом, другим нужно что-то еще, и у вас повсюду разбросаны операторы if (или, может быть, что-то более умное). Кроме того, это действительно замедляет выполнение сумасшедших запросов на основе дерева в БД.

person Parris    schedule 21.09.2012