Современные web-приложения в большинстве случаях хранят данные в SQL базах данных. Для доступа к этим данным используются объекты модели, которые позволяют совершать все основные операции: SELECT, INSERT, UPDATE, DELETE, но не ограничиваясь ими.
В этой части мы поговорим про работу с базой в Dancer2, а также научимся создавать модели и миграции на основе DBIx::Class (можно сказать, что эта статья является вводным руководством и по нему).
Для удобства будем считать, что термин база данных в нашем изложении, означает именно СУБД SQL: MySQL, MariaDB, PostgreSQL, SQLite и другие.
В perl стандартным интерфейсом для работы с БД является DBI, который использует DBD-драйверы для установки конкретных соединений (например, для интеграции с MySQL следует дополнительно к DBI установить драйвер DBD::mysql). Dancer использует модуль обертку над ним Dancer2:lugin:atabase, которая упрощает задачу создания соединения и добавляет в наш арсенал хелпер database. Установим его:
cpan Dancer2:lugin:atabase
Подключаем модуль вверху lib/MyApp.pm:
use Dancer2:lugin:atabase;
В качестве базы будем использовать SQLite, она не требует поднятия сервера и проста в использовании. Фактически нам нужен лишь драйвер:
cpan DBD::SQLite
Добавим в секцию plugins файла config.yml:
Database:
driver: "SQLite"
database: "dancr.db"
Чтобы не хранить в репозитории потенциально большой и часто изменяемый файл (в котором еще и могут оказаться персональные данные), добавьте dancr.db в .gitignore.
Теперь проверим соединение. Для этого создадим роут в lib/MyApp.pm, в котором вызовем пинг:
get '/db' => sub {
'Соединение с базой установлено' if database->ping;
};
При открытии http://localhost:5000/db мы увидим соответствующее сообщение.
Как видно из примера, общение происходит через объект, возвращаемый функцией database. Через него можно обращаться к бд и выполнять любые SQL запросы (подробный мануал можно найти в документации DBI на metacpan). Многим приложениям этого более чем достаточно. Но мы пойдем дальше и реализуем слой моделей.
Устанавливаем:
cpan Dancer2:lugin:BIC
Далее необходимо создать классы, описывающие схему данных нашего проекта. Для примера реализуем логику работы с пользователями.
Создадим корневой класс lib/MyApp/Schema.pm:
package MyApp::Schema;
use warnings;
use strict;
use parent qw/DBIx::Class::Schema/;
our $VERSION = '1.00';
__PACKAGE__->load_namespaces;
1;
Его основная задача подключать классы, представляющие конкретные таблицы. Для пользователей это будет lib/MyApp/Schema/Result/User.pm:
package MyApp::Schema::Result::User;
use warnings;
use strict;
use parent qw/DBIx::Class::Core/;
__PACKAGE__->table('user');
__PACKAGE__->add_columns(
id => {
data_type => 'integer',
is_auto_increment => 1
},
login => {
data_type => 'text',
},
email => {
data_type => 'text',
},
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_unique_constraint([qw/login/]);
__PACKAGE__->add_unique_constraint([qw/email/]);
1;
Тут мы описываем название таблицы (7 строка), потом её атрибуты и их свойства. В 22 строке определили первичный ключ и установили предикат UNIQUE для поля login в 24 и email в 26.
В результате получим следующую структуру в директории lib:
Создадим директорию db, чтобы хранить там базу и всё, что к ней относится:
mkdir db
Теперь подключаем плагин в config.yml, секция plugins:
DBIC:
default:
dsn: "dbi:SQLite:dbname=db/dancr.db"
schema_class: "MyApp::Schema"
С созданием модели мы разобрались, осталось создать таблицы и наполнить их.
Воспользуемся пакетом DBIx::Class::Migration:
cpan DBIx::Class::Migration
После установки мы сможем использовать утилиту dbic-migration для работы с миграциями. Она принимает, в том числе, следующие опции:
$base = 'dbic-migration -I lib --schema_class MyApp::Schema --dsn dbi:SQLite:dbname=db/dancr.db --target_dir db';
for (shift) {
if (/prepare/) { exec "$base prepare"; }
if (/install/) { exec "$base install"; }
}
В нем мы реализовали две простые команды:
perl migration.pl prepare
Получим следующую структуру в db:
Рассмотрим подробнее каждую поддиректорию:
Чтобы наполнить базу первоначальными данными, создадим вручную файл db/migrations/SQLite/deploy/1.00/002-users.sql:
BEGIN TRANSACTION;
INSERT INTO user ("login", "email")
VALUES ("Jenya", "j@mail.ru"),
("Mila", "m@mail.ru");
COMMIT;
Теперь применим миграции:
perl migration.pl install
get '/users' => sub {
my @users = map { { $_->get_columns } } schema->resultset('User')->all;
template 'users', {
users => \@users,
};
};
В нем мы получаем данные из базы данных и передаем их в представление в виде массива хэшей. Реализуем представление views/users.hbs:
<h1>Users</h1>
{{#each users}}
<p>логин: {{ login }}, email: {{ email }}</p>
{{/each}}
Теперь по http://localhost:5000/users можно будет увидеть список всех пользователей системы. Тем самым мы полностью реализовали основу полноценного MVC-проекта. На основании этой структуры, вы сможете легко реализовать любую бизнес задачу и создать современное web-приложение.
Весь код можно найти на гитхабе (ссылка в конце).
В этой части мы поговорим про работу с базой в Dancer2, а также научимся создавать модели и миграции на основе DBIx::Class (можно сказать, что эта статья является вводным руководством и по нему).
Для удобства будем считать, что термин база данных в нашем изложении, означает именно СУБД SQL: MySQL, MariaDB, PostgreSQL, SQLite и другие.
Подключение к БД
Dancer2 не регламентирует правила и методы для работы с базой. Разработчик волен выбрать любое подходящее решение. Для небольших приложений - это могут быть прямые SQL-запросы, для больших проектов можно реализовать DDD с репозиториями и фабриками или удобный ActiveRecord. Начнем с простого.В perl стандартным интерфейсом для работы с БД является DBI, который использует DBD-драйверы для установки конкретных соединений (например, для интеграции с MySQL следует дополнительно к DBI установить драйвер DBD::mysql). Dancer использует модуль обертку над ним Dancer2:lugin:atabase, которая упрощает задачу создания соединения и добавляет в наш арсенал хелпер database. Установим его:
cpan Dancer2:lugin:atabase
Подключаем модуль вверху lib/MyApp.pm:
use Dancer2:lugin:atabase;
В качестве базы будем использовать SQLite, она не требует поднятия сервера и проста в использовании. Фактически нам нужен лишь драйвер:
cpan DBD::SQLite
Добавим в секцию plugins файла config.yml:
Database:
driver: "SQLite"
database: "dancr.db"
Чтобы не хранить в репозитории потенциально большой и часто изменяемый файл (в котором еще и могут оказаться персональные данные), добавьте dancr.db в .gitignore.
Теперь проверим соединение. Для этого создадим роут в lib/MyApp.pm, в котором вызовем пинг:
get '/db' => sub {
'Соединение с базой установлено' if database->ping;
};
При открытии http://localhost:5000/db мы увидим соответствующее сообщение.
Как видно из примера, общение происходит через объект, возвращаемый функцией database. Через него можно обращаться к бд и выполнять любые SQL запросы (подробный мануал можно найти в документации DBI на metacpan). Многим приложениям этого более чем достаточно. Но мы пойдем дальше и реализуем слой моделей.
DBIx::Class
Для создания моделей будем использовать паттерн ActiveRecord, объединяющий в моделях бизнес логику и методы по работе с данными. В перле он реализован популярным и очень мощным модулем DBIx::Class, а Dancer имеет соответствующий плагин Dancer2:lugin:BIC.Устанавливаем:
cpan Dancer2:lugin:BIC
Далее необходимо создать классы, описывающие схему данных нашего проекта. Для примера реализуем логику работы с пользователями.
Создадим корневой класс lib/MyApp/Schema.pm:
package MyApp::Schema;
use warnings;
use strict;
use parent qw/DBIx::Class::Schema/;
our $VERSION = '1.00';
__PACKAGE__->load_namespaces;
1;
Его основная задача подключать классы, представляющие конкретные таблицы. Для пользователей это будет lib/MyApp/Schema/Result/User.pm:
package MyApp::Schema::Result::User;
use warnings;
use strict;
use parent qw/DBIx::Class::Core/;
__PACKAGE__->table('user');
__PACKAGE__->add_columns(
id => {
data_type => 'integer',
is_auto_increment => 1
},
login => {
data_type => 'text',
},
email => {
data_type => 'text',
},
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_unique_constraint([qw/login/]);
__PACKAGE__->add_unique_constraint([qw/email/]);
1;
Тут мы описываем название таблицы (7 строка), потом её атрибуты и их свойства. В 22 строке определили первичный ключ и установили предикат UNIQUE для поля login в 24 и email в 26.
В результате получим следующую структуру в директории lib:
Создадим директорию db, чтобы хранить там базу и всё, что к ней относится:
mkdir db
Теперь подключаем плагин в config.yml, секция plugins:
DBIC:
default:
dsn: "dbi:SQLite:dbname=db/dancr.db"
schema_class: "MyApp::Schema"
С созданием модели мы разобрались, осталось создать таблицы и наполнить их.
Миграции
Это набор скриптов и файлов позволяющий версионировать базу данных, а также переносить её между машинами. Миграции удобны при работе в команде. Любой разработчик может установить актуальную схему просто клонировав репозиторий проекта и запустив нужный скрипт.Воспользуемся пакетом DBIx::Class::Migration:
cpan DBIx::Class::Migration
После установки мы сможем использовать утилиту dbic-migration для работы с миграциями. Она принимает, в том числе, следующие опции:
- -I - путь к директории, где хранятся модули нашего проекта
- --schema_class - имя корневого класса
- --dsn - строка с данными для подключения к базе
- --target_dir - директория для хранения миграций
$base = 'dbic-migration -I lib --schema_class MyApp::Schema --dsn dbi:SQLite:dbname=db/dancr.db --target_dir db';
for (shift) {
if (/prepare/) { exec "$base prepare"; }
if (/install/) { exec "$base install"; }
}
В нем мы реализовали две простые команды:
- prepare - создаст весь необходимый скелет и базовые миграции, ориентируясь на нашу схему
- install - применит миграции к базе
perl migration.pl prepare
Получим следующую структуру в db:
Рассмотрим подробнее каждую поддиректорию:
- fixtures - тут хранятся фикстуры в виде json-файлы. Их обычно применяют для тестирования базы, но это выходит за рамки данной статьи.
- migrations - состоит из _source (не спроста начинается с нижнего подчеркивания) и migrations, где собственно и хранятся файлы миграции, созданные на основе нашей схемы, например 001-auto.sql (в нем вы найдете sql-код для создания таблицы users).
Чтобы наполнить базу первоначальными данными, создадим вручную файл db/migrations/SQLite/deploy/1.00/002-users.sql:
BEGIN TRANSACTION;
INSERT INTO user ("login", "email")
VALUES ("Jenya", "j@mail.ru"),
("Mila", "m@mail.ru");
COMMIT;
Теперь применим миграции:
perl migration.pl install
Выводим данные (вместо заключения)
Напишем простой роут, по которому будем отдавать список пользователей:get '/users' => sub {
my @users = map { { $_->get_columns } } schema->resultset('User')->all;
template 'users', {
users => \@users,
};
};
В нем мы получаем данные из базы данных и передаем их в представление в виде массива хэшей. Реализуем представление views/users.hbs:
<h1>Users</h1>
{{#each users}}
<p>логин: {{ login }}, email: {{ email }}</p>
{{/each}}
Теперь по http://localhost:5000/users можно будет увидеть список всех пользователей системы. Тем самым мы полностью реализовали основу полноценного MVC-проекта. На основании этой структуры, вы сможете легко реализовать любую бизнес задачу и создать современное web-приложение.
Весь код можно найти на гитхабе (ссылка в конце).
P.S.
Следующая часть будет заключительной. Там будет самое интересное!Серия статей про Dancer2
- Часть I - установка, роутинг и шаблоны
- Часть II - выбор шаблонного движка, сессии и флэш-сообщения
- Часть III (текущая) - работа с базой, модели и миграции
Ссылки
- DBI
- DBD::SQLite
- DBD::mysql
- Dancer2:lugin:atabase
- DBIx::Class
- Dancer2:lugin:BIC
- DBIx::Class::Migration
- Репозиторий с кодом
Dancer2 или современное web-приложение на PERL. Часть III
Современные web-приложения в большинстве случаях хранят данные в SQL базах данных. Для доступа к этим данным используются объекты модели, которые позволяют совершать все основные операции: SELECT,...
habr.com