Bug Tracker: модель и страницы приложения (часть пятая)

Владимир | | Ajax, CodeIgniter, HTML, PHP, Web разработка.

bug_tracker_logo_part5

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

Основная проблема была в том, что я не продумал до конца, что именно будет отображаться на страницах баг трекера, но взялся за работу с базой.

В общем-то, страниц у баг трекера не много. Первоначально я вообще хотел сделать одностраничное приложение, но передумал из-за проблем с индексацией поисковиками.

Поэтому сегодня постараюсь максимально подробно осветить этот вопрос.

Т.к. индексация страниц баг трекера поисковиками очень желательна, то при разработке имеет смысл придерживаться рекомендаций от Google, а именно методики Progressive Enhancement (постепенного улучшения).

Идея следующая. Нужно сделать приложения так, чтобы без поддержки JavaScript (только с помощью обычных ссылок) можно было получить доступ к любой информации (багам и комментариям).

После этого, с помощью JavaScript вносятся различные улучшения. Например, без JavaScript при клике на заголовке бага посетитель попадет на страницу с его описанием и комментариями.

Если JavaScript включен, то после загрузки страницы обычные ссылки будут преобразованы в ajax-ссылки и вместо перехода на другую страницу будет подгужен список комментариев.

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

В результате получается, что для работы баг трекера нужны страницы двух типов.

1) Страницы с общим перечнем багов. Они будут содержать заголовок и описание бага (без комментариев). Сюда относятся: главная и страницы категорий. Тут же будет форма для добавления сообщения о новом баге.

2) Страницы отдельных багов. Тут будет полная информация о баге и дерево комментариев. Плюс форма добавления комментария.

Кстати, из страниц первого типа можно будет сформировать RSS ленты (как предлагал Алексей Качаев).

Теперь посмотрим, что нам нужно сделать для того, чтобы сформировать содержимое этих страниц.

Как ни странно, но практически вся работа сделана в прошлой части 😉 .

У нас есть библиотека, которая позволяет преобразовывать таблицу с багами и комментариями в html списки. При этом совершенно не важно, сколько багов и комментариев находится в этой таблице.

Например, если мы получим из базы таблицу только с информацией о багах (без комментариев), то сможем точно также сформировать html список.

Кроме того, у нас есть шаблоны для отображения багов и комментариев. И совершенно неважно как их использовать, для создания списка багов с комментариями или без них.

Тем не менее, в библиотеку нужно внести несколько мелких исправлений, чтобы сделать её более универсальной.

1) Убрать array_slice. Дело в том, что я сразу не разобрался как ограничить количество результатов, которые возвращает SQL запрос при использовании объединений (JOINS), но потом исправил эту ошибку.

2) Добавить метод getHTMLCommentsList, который будет использоваться для преобразования массива с комментариями (без данных о баге) в HTML список. Этот метод потребуется при загрузке списка комментариев с помощью AJAX.

function getHTMLCommentsList($comments, $config = null) {
	if (!empty($config)) {
		foreach ($config as $key => $value) {
			$this->config[$key] = $value;
		}
	}
	$this->CI->load->library('parser');
	$html = $this->_convert2HTMLList($comments, 0);
	return $html;
}

Этот метод по сути представляет собой оболочку, для _convert2HTMLList, описанного в прошлой части.

3) Добавить проверку наличия полей с данными о комментариях (в методе _convert2SimpleTree). Т.к. теперь этот метод может получить данные багов как с комментариями так и без них.

Исходники библиотеки можно скачать в виде архива. Ссылка в конце страницы.

Кроме того, я думаю, не стоит отказываться от возможности получения полного списка багов с комментариями. По крайней мере, есть один случай, в котором это может быть полезно. Речь о том, что кроме RSS можно организовать отправку сообщений о новых багах на eMail, например, раз в день. И в таких письмах имеет смысл отправить не только описания багов, но и комментарии к ним. Ведь если между добавлением бага и отправкой письма может пройти несколько часов, то, скорее всего и комментарии появятся.

Также, думаю, стоит рассмотреть основные SQL запросы, с помощью которых можно будет получить информацию для перечисленных выше страниц. Диаграмма со структурой таблиц приведена в третьей части.

1) Получение списка багов (используется для создания главной страницы и страниц категорий).

SELECT b.* , c.link, c.name AS category
	FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id = c.id
	ORDER BY b.bug_date DESC LIMIT ?, ?

Обратите внимание на LIMIT в конце запроса. С его помощью мы ограничиваем число багов на странице. Естественно, при этом будет использоваться библиотека пагинации из CodeIgniter.

2) Получение списка багов для выбранной категории.

SELECT COUNT(b.id) AS catsnum FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id = c.id
	WHERE c.link=?

3) Получение подробной информации о баге и всех его комментариев.

SELECT b.*, c.link, c.name AS category,
	cm.id AS c_id, cm.uname AS c_uname, cm.description AS c_description,
	cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail
	FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id=c.id
	LEFT JOIN comments AS cm ON b.id=cm.bug_id WHERE b.id=?

4) Получение отдельно списка комментариев выбранного бага (для работы в ajax-режиме). По-моему, не имеет смысла использовать отдельный запрос для решения этой задачи. Ведь все необходимые данные можно получить предыдущим запросом.

5) Получение списка багов и комментариев (я приводил этот запрос в прошлый раз, но сейчас немного изменил его и ввел ограничение на количество полей).

SELECT b.* , c.link, c.name AS category, cm.id AS c_id,
	cm.uname AS c_uname, cm.description AS c_description,
	cm.comment_date, cm.parent_id, cm.bug_id, cm.uemail AS c_uemail
	FROM comments AS cm
	RIGHT JOIN (
		SELECT * FROM bugs ORDER BY bugs.bug_date DESC
		LIMIT ?, ?
	) b ON b.id = cm.bug_id
	LEFT JOIN categories AS c ON b.category_id = c.id

Тут стоит обратить внимание на строку 7.

6) Получение общего количества багов (используется для пагинации)

function getBugsCount() {
	return $this->db->count_all('bugs');
}

Это не SQL запрос 😉 Здесь я использовал встроенную функцию CodeIgniter. На самом деле выполняется

SELECT COUNT(*) AS 'numrows' FROM 'bugs'

7) Получение общего количества багов в выбранной категории

SELECT COUNT(b.id) AS catsnum FROM bugs AS b
	LEFT JOIN categories AS c ON b.category_id = c.id
	WHERE c.link=?

Я не буду подробно описывать принцип работы каждого запроса. В прошлой части я довольно подробно останавливался на одном из самых сложных (пятый запрос в этом списке). Самое главное четко представлять как работают объединения таблиц (JOINS).

Вместо вопросительно знака в запросы будут подставлены конкретные данные.

В завершение приведу ссылку на ахив с исходниками библиотеки table2tree.

Как видите, изменений минимум. На этом примере очень хорошо видно одно из основных преимуществ архитектуры MVC. За счет того, что вся работа с базой данных вынесена в модель можно расширять возможности приложения практически без изменений в остальной части кода.

До встречи!

P.S. Как всегда я рад выслушать любые замечания и предложения.

  • Ты вот уже не первый пост пишешь, что индексация багтрекера поисковиками желательна. Зачем? Не вижу никакого смысла. Там полезной информации: чейнджлог и ДСП. Остальное — функционал.
    Не рекламу же ты вставлять в багтрекер собрался? Или так важно, чтобы гугл мог о каждом баге the проекта выдать ссылку?

    • Нет, дело не в рекламе. Разве что если речь идет о рекламе официального сайта приложения для которого баг трекер создан.

      Но если пользователь наткнется на баг и начнет искать о нем информацию (через поисковик), то он с большой вероятностью наткнется на соответствующую запись в баг трекере (если она есть, конечно). И по крайней мере получит хоть какую-ту информацию. В любом случае такой поиск будет быстрее чем отправка письма в службу поддержки.

      Это особенно актуально для недавно открывшихся проектов. Когда не полностью написана документация, нет форума и т.д.

      Конечно, если баг трекером будут пользоваться только тестировщики и разработчики, то об индексации можно и не заботиться.

      • Спасибо. Разумно. Хотя только из этих соображений я бы принимать решение не стал. Как имхо, это соображение может быть дополнительным доводом для принятия решения. Всё равно чтобы найти баг, надо догадаться поискать (а далеко не каждый пользователь стал бы), надо правильно сформулировать (как в трекере записано), нужно чтобы формулировка была более-менее уникальной, нужно чтобы поисковик успел проиндексировать.
        Более-менее внятные шансы — только когда пользователь зашёл на сайт проекта и сунулся в трекер, а уже потом сообразил поискать по трекеру с помощью гугля. (Ну, или если к трекеру гуглопоиск прикручен)

        • >> надо правильно сформулировать

          Из собственного опыта. Если пользователь видит хоть какое-то сообщение с описанием ошибки, то обычно достаточно поискать его по точному соответствию (в кавычках). Есть очень высокая вероятность, что в баг трекере это сообщение будет приведено один в один.

          >> если к трекеру гуглопоиск прикручен

          Прикручивать имеет смысл только если баг трекер проиндексирован.

          А в общем, я согласен. Баг трекер — это не замена документации или форума.

  • Ты вот уже не первый пост пишешь, что индексация багтрекера поисковиками желательна. Зачем? Не вижу никакого смысла. Там полезной информации: чейнджлог и ДСП. Остальное — функционал.
    Не рекламу же ты вставлять в багтрекер собрался? Или так важно, чтобы гугл мог о каждом баге the проекта выдать ссылку?

    • Нет, дело не в рекламе. Разве что если речь идет о рекламе официального сайта приложения для которого баг трекер создан.

      Но если пользователь наткнется на баг и начнет искать о нем информацию (через поисковик), то он с большой вероятностью наткнется на соответствующую запись в баг трекере (если она есть, конечно). И по крайней мере получит хоть какую-ту информацию. В любом случае такой поиск будет быстрее чем отправка письма в службу поддержки.

      Это особенно актуально для недавно открывшихся проектов. Когда не полностью написана документация, нет форума и т.д.

      Конечно, если баг трекером будут пользоваться только тестировщики и разработчики, то об индексации можно и не заботиться.

      • Спасибо. Разумно. Хотя только из этих соображений я бы принимать решение не стал. Как имхо, это соображение может быть дополнительным доводом для принятия решения. Всё равно чтобы найти баг, надо догадаться поискать (а далеко не каждый пользователь стал бы), надо правильно сформулировать (как в трекере записано), нужно чтобы формулировка была более-менее уникальной, нужно чтобы поисковик успел проиндексировать.
        Более-менее внятные шансы — только когда пользователь зашёл на сайт проекта и сунулся в трекер, а уже потом сообразил поискать по трекеру с помощью гугля. (Ну, или если к трекеру гуглопоиск прикручен)

        • >> надо правильно сформулировать

          Из собственного опыта. Если пользователь видит хоть какое-то сообщение с описанием ошибки, то обычно достаточно поискать его по точному соответствию (в кавычках). Есть очень высокая вероятность, что в баг трекере это сообщение будет приведено один в один.

          >> если к трекеру гуглопоиск прикручен

          Прикручивать имеет смысл только если баг трекер проиндексирован.

          А в общем, я согласен. Баг трекер — это не замена документации или форума.

  • Terry

    Если можно — то задам вопрос по теме:
    Есть две таблицы: табл1 и табл2.
    Каждой строке из табл2 соответствует несколько строк из табл1. Тоесть насколько я понимаю должна быть связь один ко многим. Но не могу понять как это реализовать на функционале mysql? Расскажите пожалуйста. Заранее спасибо.

    • Terry

      UPD
      дополнение: еще хочу уточнить на каждую запись из табл1 может ссылаться несколько записей из табл2. Тоесть связь должна быть много ко многим

      • Если связь «много-ко-многим», то нужна дополнительная таблица.
        У меня есть статья о создании облака тегов, думаю в качестве примера подойдет 😉

        • Terry

          Спасибо большое. Именно то что нужно.
          Кстати, использую MySQL Workbench — удобная штука, и даже есть возможность експортировать в изображение.

        • В бесплатной версии этой возможности нет. Я скриншоты делаю 🙂

        • Terry

          У меня версия для винды, с офф сайта, работает без всяких лекарств.

        • У меня точно такая же. Модели и рисунки нормально создаются, но экспортировать (сохранить) я их не могу.

  • Terry

    Если можно — то задам вопрос по теме:
    Есть две таблицы: табл1 и табл2.
    Каждой строке из табл2 соответствует несколько строк из табл1. Тоесть насколько я понимаю должна быть связь один ко многим. Но не могу понять как это реализовать на функционале mysql? Расскажите пожалуйста. Заранее спасибо.

    • Terry

      UPD
      дополнение: еще хочу уточнить на каждую запись из табл1 может ссылаться несколько записей из табл2. Тоесть связь должна быть много ко многим

      • Если связь «много-ко-многим», то нужна дополнительная таблица.
        У меня есть статья о создании облака тегов, думаю в качестве примера подойдет 😉

        • Terry

          Спасибо большое. Именно то что нужно.
          Кстати, использую MySQL Workbench — удобная штука, и даже есть возможность експортировать в изображение.

        • В бесплатной версии этой возможности нет. Я скриншоты делаю 🙂

        • Terry

          У меня версия для винды, с офф сайта, работает без всяких лекарств.

        • У меня точно такая же. Модели и рисунки нормально создаются, но экспортировать (сохранить) я их не могу.

  • Big_Shark

    Прочел статью и такое чувство что статья не о чем.
    Все что вы описали в этой статье уже было по чуть чуть затронуто в каждой пред идущей и в комментариях к ним.
    Плохо что нету актив рекорд и почти все запросы сложные.

    • be3

      Active Record в CI ужасная штука +) Ею можно побаловаться, но использовать в проектах я бы не стал
      1. Сложные запросы на AC просто невозможно написать.
      2. Sql запросы написанные без использования AC выполняются быстрее.

      • Big_Shark

        Зато АС будет работать на любой платформе будь то MySQL или что либо другое.
        С помощью АС можно быстрей писать код так как не нужно заморачиваться с ручным описанием всем WHERE условий.
        Сложные запросы в 99 процентах случаях не нужны так как в большинстве случаев 1 сложный запрос выполняется дольше чем 2 простых.

  • Big_Shark

    Прочел статью и такое чувство что статья не о чем.
    Все что вы описали в этой статье уже было по чуть чуть затронуто в каждой пред идущей и в комментариях к ним.
    Плохо что нету актив рекорд и почти все запросы сложные.

    • be3

      Active Record в CI ужасная штука +) Ею можно побаловаться, но использовать в проектах я бы не стал
      1. Сложные запросы на AC просто невозможно написать.
      2. Sql запросы написанные без использования AC выполняются быстрее.

      • Big_Shark

        Зато АС будет работать на любой платформе будь то MySQL или что либо другое.
        С помощью АС можно быстрей писать код так как не нужно заморачиваться с ручным описанием всем WHERE условий.
        Сложные запросы в 99 процентах случаях не нужны так как в большинстве случаев 1 сложный запрос выполняется дольше чем 2 простых.

  • >> такое чувство что статья не о чем

    Тут вы правы 🙂 Все архитектурные вопросы уже обсуждались, а здесь только примеры

    >> Зато АС будет работать на любой платформе будь то MySQL или что либо другое

    По идее SQL код будет работать на любой платформе если в нем нет специфичных (для конкретной базы) операторов.

    >> не нужно заморачиваться с ручным описанием всем WHERE условий

    В AC вы тоже пишите их вручную, только в другом формате 😉
    К тому же Active Record не единственный паттерн для работы с БД. Например, есть еще Data Gateway, Data Mapper и ORM.

    Стоит ли их использовать — вопрос другой. Быстродействие в любом случае снижается.

    Мне лично в AC не нравиться то, что цепочка вызовов методов AC практически дублирует SQL запрос. И работа при этом практически не ускоряется.

    >> 1 сложный запрос выполняется дольше чем 2 простых

    все зависит от запроса и конкретной базы

  • >> такое чувство что статья не о чем

    Тут вы правы 🙂 Все архитектурные вопросы уже обсуждались, а здесь только примеры

    >> Зато АС будет работать на любой платформе будь то MySQL или что либо другое

    По идее SQL код будет работать на любой платформе если в нем нет специфичных (для конкретной базы) операторов.

    >> не нужно заморачиваться с ручным описанием всем WHERE условий

    В AC вы тоже пишите их вручную, только в другом формате 😉
    К тому же Active Record не единственный паттерн для работы с БД. Например, есть еще Data Gateway, Data Mapper и ORM.

    Стоит ли их использовать — вопрос другой. Быстродействие в любом случае снижается.

    Мне лично в AC не нравиться то, что цепочка вызовов методов AC практически дублирует SQL запрос. И работа при этом практически не ускоряется.

    >> 1 сложный запрос выполняется дольше чем 2 простых

    все зависит от запроса и конкретной базы

  • Big_Shark

    Active Record не единственный но самый быстрый и он встроен в CI

    Стоит ли их использовать — вопрос другой. Быстродействие в любом случае снижается.

    Через ORM быстродействие сильно снижается а через Active Record очень незначительно.

    все зависит от запроса и конкретной базы

    Это верно но.

    • а через Active Record очень незначительно

      Согласен, но ведь и работу Active Record практически не упрощает.

      Это верно но.

      Провел небольшой тест. Это, конечно, очень не точно, но сложный запрос выполнился быстрее двух простых 😉

      • Big_Shark

        Так вы в простые запросы добавьте тоже LIMIT и WHERE
        Тест конечно очень приблизительный)))

        Согласен, но ведь и работу Active Record практически не упрощает.

        Несогласен мне с ним допустим намного легче работать чем без него так как я не заморачиваюсь с тем сколько у меня WHERE условий было ли уже такое условие или с WHERE IN я просто получил массив ID и передал его в эту переменную а так мне следует его разбить запятыми с кавычками и дописать в запрос.
        Я сейчас поддерживаю несколько сайтов и только на 1 из них Active Record и разницу в легкости работы с ними я очень замечаю
        То что я на Active Record рекорд делаю в 2-3 строки без него я делаю в 5-6 и более.
        Все это уходит лишь на преобразования данных для запроса.

        • Так вы в простые запросы добавьте тоже LIMIT и WHERE

          Без разницы 🙂 В таблице bugs около 10 записей, а в categories — вообще 3.

          А насчет удобства записи AR вы правы. Этот момент я почему-то упустил из виду. Цепочка вызовов AR выглядит гораздо аккуратнее, чем разорванная строка SQL запроса.

          Просто у меня было несколько ситуаций когда я знал как написать запрос на чистом SQL, но для того, чтобы переписать его с использованием AR приходилось постоянно смотреть мануал.

          Особенно не нравится когда приходится использовать знаки сравнения в названиях ключей:
          $this->db->having(array('title =' => 'My Title', 'id <' => $id));
          // Produces: HAVING title = 'My Title', id < 45

          (пример из мануала CI)

          Хотя, это, конечно, дело привычки.

    • Хочу уточнить. Похоже такой результат получился благодаря внутреннему кэшированию MySql

  • Big_Shark

    Active Record не единственный но самый быстрый и он встроен в CI

    Стоит ли их использовать — вопрос другой. Быстродействие в любом случае снижается.

    Через ORM быстродействие сильно снижается а через Active Record очень незначительно.

    все зависит от запроса и конкретной базы

    Это верно но.

    • а через Active Record очень незначительно

      Согласен, но ведь и работу Active Record практически не упрощает.

      Это верно но.

      Провел небольшой тест. Это, конечно, очень не точно, но сложный запрос выполнился быстрее двух простых 😉

      • Big_Shark

        Так вы в простые запросы добавьте тоже LIMIT и WHERE
        Тест конечно очень приблизительный)))

        Согласен, но ведь и работу Active Record практически не упрощает.

        Несогласен мне с ним допустим намного легче работать чем без него так как я не заморачиваюсь с тем сколько у меня WHERE условий было ли уже такое условие или с WHERE IN я просто получил массив ID и передал его в эту переменную а так мне следует его разбить запятыми с кавычками и дописать в запрос.
        Я сейчас поддерживаю несколько сайтов и только на 1 из них Active Record и разницу в легкости работы с ними я очень замечаю
        То что я на Active Record рекорд делаю в 2-3 строки без него я делаю в 5-6 и более.
        Все это уходит лишь на преобразования данных для запроса.

        • Так вы в простые запросы добавьте тоже LIMIT и WHERE

          Без разницы 🙂 В таблице bugs около 10 записей, а в categories — вообще 3.

          А насчет удобства записи AR вы правы. Этот момент я почему-то упустил из виду. Цепочка вызовов AR выглядит гораздо аккуратнее, чем разорванная строка SQL запроса.

          Просто у меня было несколько ситуаций когда я знал как написать запрос на чистом SQL, но для того, чтобы переписать его с использованием AR приходилось постоянно смотреть мануал.

          Особенно не нравится когда приходится использовать знаки сравнения в названиях ключей:
          $this->db->having(array('title =' => 'My Title', 'id <' => $id));
          // Produces: HAVING title = 'My Title', id < 45

          (пример из мануала CI)

          Хотя, это, конечно, дело привычки.

    • Хочу уточнить. Похоже такой результат получился благодаря внутреннему кэшированию MySql

  • И что следует после но? если не секрет?

  • И что следует после но? если не секрет?

  • Big_Shark

    Ну знаки сравнения используються не так уж и часто.
    Когда мне необходимо их использовать я часто пишу $this->db->where('id db->where('`id` < '.$id);

    для того, чтобы переписать его с использованием AR приходилось постоянно смотреть мануал.

    Это так сказаться непривычки.
    Если пользоваться им постоянно но уже на автомате пишешь запросы на АС.

  • Big_Shark

    Ну знаки сравнения используються не так уж и часто.
    Когда мне необходимо их использовать я часто пишу $this->db->where('id db->where('`id` < '.$id);

    для того, чтобы переписать его с использованием AR приходилось постоянно смотреть мануал.

    Это так сказаться непривычки.
    Если пользоваться им постоянно но уже на автомате пишешь запросы на АС.

  • tester_grep

    Пользуюсь этим фреймворком уже несколько лет. Удобная справка и вообще хорошо документирован.
    Кстати, появилось руководство пользователя на русском для версии 2.0.0
    http://codeigniter.su