Выпуск 6. Август 2013
← Подготовка к YAPC::Europe 2013 | Содержание | Секретные операторы Perl и не только →DBIx::Class в примерах
DBIx::Class — это один из самых популярных ORM для Perl для выполнения SQL-запросов к базам данных через объектно-ориентированный интерфейс. Этот инструмент, при хорошем уровне владения, позволяет быстро разрабатывать приложения, работающие с данными в реляционных базах, позволяя абстрагироваться от языка SQL и нюансов его реализации в конкретной СУБД и оперировать привычными для программиста классами и методами для работы с данными
Зачем нужен DBIx::Class да и ORM вообще?
Аббревиатура ORM
означает Object-relational mapping (Объектно-реляционное отображение) и представляет собой технику, которая позволяет преобразовывать данные из представления их в реляционной базе (таблицы, строки, столбцы и запросы) в объектное представление (объекты, атрибуты и методы).
Разрабатывая программы с использованием объектно-ориентированной парадигмы, программисту привычнее и естественнее обращаться к данным как к объектам, создавая или удаляя их с помощью привычных операторов, применяя методы для изменения атрибутов и т.д. Необходимость использования реляционной базы данных добавляет программисту новый уровень сложности в разработке: использование языка SQL для манипуляции с данными. Это в общем случае приводит к замедлению разработки (время на составление и проверку SQL-запросов), частому дублированию строк с запросами, плохой читаемости такого кода (особенно когда требуется собрать конечную строку SQL запроса из отдельных кусочков) и необходимости переделки всех запросов при добавлении поддержки другой базы данных (или миграции).
Сравнить это можно с поваром ресторана, который готовит еду и периодически выбегает на огород, чтобы раскидать навоз на грядки, прополоть овощи и выкопать картошки для нового блюда. При этом надо не забыть каждый раз переодеться и вымыть руки. А если времени становится мало (дедлайн), то можно увидеть, что повар начинает нарезать лук на грядке или перетаскивать в тележке клумбы с зеленью на кухню, чтобы поменьше выбегать на огород.
Таким образом, ORM создаёт посредника между базой данных и ООП-кодом. С одной стороны это упрощает работу с базой данных, сокращает код и уменьшает время разработки. С другой стороны эта прослойка в некоторых ситуациях может оказаться неэффективной, поскольку генерируемые SQL-запросы могут оказаться медленными или может оказаться, что они разбиваются на несколько подзапросов, увеличивая нагрузку на СУБД.
История DBIx::Class
Проект основал в 2005 году Matt S. Trout как альтернативу Class::DBI
. Поскольку Class::DBI
в своё время был вещью достаточно уникальной и имел огромную пользовательскую базу, то попытка сделать его коммерческим продуктом с закрытым кодом привела к тому, что, во-первых, возник сам проект DBIx::Class
, а во-вторых, он сразу получил большое количество пользователей-перебежчиков и разработчиков, которые начали активно использовать и разрабатывать библиотеку.
В 2008 году на роль «бензопильщика» (chainsaw delegate — шуточное прозвище для сопровождающего) был назначен Peter Rabbitson, который заведует релизами проекта и по сей день. Совсем недавно Peter решил поставить вопрос ребром о своём статусе в данном проекте, поскольку на данный момент он практически является той «единой точкой отказа»: если по каким-то причинам он не может уделить времени проекту, то проект просто замораживается и не развивается, так как не находится такого смелого «бензопильщика», который смог бы подхватить проект. Это вынуждает его снова и снова возвращаться, чтобы срочно сделать накопившиеся задачи. Peter готов полностью сконцентрироваться на DBIx::Class
, найти финансирование и написать долгосрочный план развития, сообществу нужно лишь подкрепить его уверенность в своих силах словами поддержки и одобрения.
Таким образом на сегодняшний день модуль имеет отличные перспективы по дальнейшему развитию и поддержке.
Создание приложения на DBIx::Class
На момент создания статьи текущей стабильной версией DBIx::Class
является версия 0.08250, вышедшая в конце апреля 2013 г., имеющая заметные улучшения в производительности кода. Поэтому имеет смысл перед началом изучения поставить самую свежую версию модуля, чтобы получить адекватное представление о нём.
Для лучшего понимания начнём изучение DBIx::Class
на примере разработки веб-приложения для форума или коллективного блога. Блоги и форумы — это хороший образец приложений, в которых используется база данных для хранения создаваемого контента, где присутствуют как операции создания и удаления записей, так и чтения и поиска.
Блог будет построен на основе веб-фреймворка Dancer
. Создадим базовую структуру приложения с названием Web::Log
:
$ dancer -a Web::Log
$ cd Web-Log
Теперь можно перейти к проектированию приложения.
Схема базы данных
В самом простом случае блог содержит три основные сущности: авторы, посты и комментарии. Таким образом в схеме базы данных будут присутствовать три таблицы: authors
, posts
, comments
. Так будет выглядеть SQL-запрос для создания базы (диалект SQLite):
CREATE TABLE authors (
authorid INTEGER PRIMARY KEY AUTOINCREMENT,
author VARCHAR(255) UNIQUE
);
CREATE TABLE posts (
postid INTEGER PRIMARY KEY AUTOINCREMENT,
post TEXT,
postime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
authorid REFERENCES authors(authorid)
);
CREATE TABLE comments (
commentid INTEGER PRIMARY KEY AUTOINCREMENT,
comment TEXT,
comtime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
authorid REFERENCES authors(authorid),
postid REFERENCES posts(postid)
);
В данной схеме мы создаём для каждой таблицы отдельный первичный ключ, даже несмотря на то, что есть другие уникальные поля, которые могут быть использованы в этом качестве. Это нужно прежде всего для удобного использования внешних ключей, кроме того это является одним из требований DBIx::Class
к таблицам.
Схема также иллюстрирует отношения: автор может создавать несколько постов и комментариев, поэтому таблицы posts и comments имеют внешний ключ authorid
, у каждого поста могут быть свои комментарии, поэтому таблица comments
имеет также внешний ключ на postid
.
Объектное представление схемы базы данных
Для работы с базой данных с помощью DBIx::Class
мы должны создать классы для данной схемы. В терминах данной ORM схема базы данных — это отдельный класс, задающий пространство имён для базы, например, Web::Log::Schema
, который мы загружаем и используем в коде. Каждая таблица представляет собой отдельный класс в пространстве имён Web::Log::Schema::Result
. В качестве имени класса используется имя таблицы без окончания множественного числа — s
.
Существует два варианта создания необходимых классов: ручной и автоматизированный. Рассмотрим самый простой — автоматизированный, чтобы увидеть как должны выглядеть наши классы. Для этого воспользуемся утилитой dbicdump
из дистрибутива DBIx::Class::Schema::Loader
, которая умеет генерировать классы из существующего экземпляра базы данных и автоматически формировать их POD-документацию.
Создадим базу данных (SQLite) из описанного выше SQL (weblog.sql
):
$ sqlite3 weblog.db < weblog.sql
Генерируем классы:
$ dbicdump -o dump_directory=./lib \
-o components='["InflateColumn::DateTime"]' \
Web::Blog::Schema dbi:SQLite:./weblog.db
Классы создаются под директорией lib
, для классов таблиц (Result) подключается модуль InflateColumn::DateTime
, который позволяет автоматически преобразовывать данные типа datetime
при извлечении в объект DateTime
, также задаётся пространство имён Web::Log::Schema
и указывается стандартная строка подключения к существующей базе данных (dsn
).
В результате создаются все необходимые классы, рассмотрим их по порядку. Главный класс схемы Web::Log::Schema
:
use utf8;
package Web::Log::Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces;
1;
Данный класс является наследником DBIx::Class::Schema
. Метод load_namespaces
производит загрузку всех прочих классов таблиц в пространстве имён Web::Log::Schema::Result
.
Класс Web::Log::Schema::Result::Author
представляет таблицу authors
.
use utf8;
package Web::Log::Schema::Result::Author;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->load_components("InflateColumn::DateTime");
__PACKAGE__->table("authors");
__PACKAGE__->add_columns(
"authorid",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"author",
{ data_type => "varchar", is_nullable => 0, size => 255 },
);
__PACKAGE__->set_primary_key("authorid");
__PACKAGE__->add_unique_constraint("author_unique", ["author"]);
__PACKAGE__->has_many(
"comments",
"Web::Log::Schema::Result::Comment",
{ "foreign.authorid" => "self.authorid" },
{ cascade_copy => 0, cascade_delete => 0 },
);
__PACKAGE__->has_many(
"posts",
"Web::Log::Schema::Result::Post",
{ "foreign.authorid" => "self.authorid" },
{ cascade_copy => 0, cascade_delete => 0 },
);
1;
Метод table
указывает название таблицы, метод add_column
задаёт поля таблицы и их атрибуты. Метод set_primary_key
задаёт первичный ключ таблицы, а метод add_unique_constraint()
позволяет указать, что поле author
–— уникальное. С помощью метода has_many
мы можем выразить отношения между таблицами. В данном случае указывается, что от данной таблицы зависят две другие таблицы comments
и posts
, имеющие внешний ключ на поле authorid
.
Класс Web::Log::Schema::Result::Post
представляет таблицу posts:
use utf8;
package Web::Log::Schema::Result::Post;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->load_components("InflateColumn::DateTime");
__PACKAGE__->table("posts");
__PACKAGE__->add_columns(
"postid",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"post",
{ data_type => "text", is_nullable => 1 },
"postime",
{
data_type => "datetime",
default_value => \"current_timestamp",
is_nullable => 0,
},
"authorid",
{ data_type => "", is_foreign_key => 1, is_nullable => 1 },
);
__PACKAGE__->set_primary_key("postid");
__PACKAGE__->belongs_to(
"authorid",
"Web::Log::Schema::Result::Author",
{ authorid => "authorid" },
{
is_deferrable => 0,
join_type => "LEFT",
on_delete => "NO ACTION",
on_update => "NO ACTION",
},
);
__PACKAGE__->has_many(
"comments",
"Web::Log::Schema::Result::Comment",
{ "foreign.postid" => "self.postid" },
{ cascade_copy => 0, cascade_delete => 0 },
);
1;
По сравнению с предыдущим классом здесь появляется метод belongs_to
, который отражает отношения между таблицей posts
и authors
: использование внешнего ключа authorid
. По смыслу belongs_to
противоположен has_many
.
Последний класс Web::Log::Schema::Result::Comment
представляет таблицу comments (аналогичен предыдущим, поэтому листинг не приводится).
Второй вариант создания классов — ручной. В этом случае все четыре класса пришлось бы писать в ручную. Но с другой стороны это позволило бы быстро создать базу данных, не используя ни строчки SQL-кода с помощью метода deploy
:
use Web::Log::Schema;
Web::Log::Schema->connect('dbi:SQLite:weblog.db')->deploy;
Это открывает возможность для прозрачной миграции приложения на произвольную базу данных, сменив лишь одну строку подключения!
Подключение к базе данных
В приложении потребуется делать подключение к базе данных. Поскольку процесс приложения будет постоянно присутствовать в памяти и последовательно обрабатывать запросы, то выгоднее иметь одно постоянное подключение к базе данных, а не открывать и закрывать его на каждый запрос. В этом случае нам удобно создать специальный класс-синглтон для подключения к базе данных.
package Web::Log::DB;
use strict;
use warnings;
use Web::Log::Schema;
my $db;
my $db_file = $ENV{'WEB_LOG_DB'} || 'weblog.db';
# db singleton
sub db {
$db ||= Web::Log::Schema->connect('dbi:SQLite:'. $db_file);
}
1;
Как видно, подключается модуль базы данных Web::Log::Schema
, который автоматически выполняет загрузку всех модулей таблиц. Создаётся приватная переменная $db
объекта базы данных. Первый вызов функции db
этого модуля создаст подключение к базе данных, все последующие вызовы в рамках одного процесса будут возвращать существующее подключение.
RESTful API
Воспользуемся популярной парадигмой REST при проектировании API веб-приложения и создадим перечень ресурсов и операций над ними, которые будут соответствовать задачам нашего приложения. Такой подход упрощает разработку, поскольку позволяет разбить сложную задачу на несколько независимых простых подзадач, позволяя в будущем легко дополнять и развивать интерфейс.
Сначала создадим модуль контроллера API веб-приложения Web::Log::API
:
package Web::Log::API;
use Dancer ':syntax';
use Web::Log::DB;
prefix '/api';
set serializer => 'JSON';
set content_type => 'application/json';
...
Для тех, кто не знаком с фреймворком Dancer, поясню, что создаётся модуль контроллера, URI всех маршрутов которого будет иметь префикс /api
. Все возвращаемые Perl-структуры данных будут проходить сериализацию в формат JSON
и будет устанавливаться заголовок типа данных application/json
. Таким образом, мы заодно определяем и представление наших данных.
Авторы
Наше приложение достаточно простое и нам требуется иметь возможность создавать новых авторов и получать список существующих авторов.
На каждое выполняемое действие определяется HTTP-ресурс:
- Список авторов — http-запрос
GET /api/authors
- Добавление нового автора — http-запрос
PUT /api/authors/:author
Реализуем код таких операций:
1). Список авторов
get '/authors' => sub {
my $db = Web::Log::DB->db;
my $rs = $db->resultset('Author');
[ $rs->get_column('author')->all ];
};
В первую очередь в маршруте получаем объект подключения к базе данных — $db
. Затем применяется метод resultset
для получения одноимённого объекта. ResultSet можно отождествить с запросом к заданной таблице базы данных для получения результата. Важно понимать, что сам по себе вызов метода resultset
не приводит к выполнению SQL-запроса, он лишь формирует необходимые данные для выполнения такого запроса. Обратите внимание, что под значением аргумента resultset
Author
имеется ввиду не имя таблицы в базе данных, а имя класса Web::Log::Schema::Result::Author
.
Последующий вызов get_column
уточняет, какой столбец нас интересует для извлечения, ну а метод all
уже непосредственно формирует и исполняет запрос к базе данных и возвращает обычный массив с результатами выборки. Можно сказать, что данная последовательность вызовов эквивалентна одному SQL-запросу:
SELECT author FROM authors
2). Следующий маршрут для добавления нового автора
put '/authors/:author' => sub {
my $author = param('author');
my $db = Web::Log::DB->db;
my $rs = $db->resultset('Author');
eval {
$rs->create( { author => $author } );
};
{ status => $@ ? 'failure' : 'success' };
};
Получив нужное значение имени автора из параметра в URI, выполняется запрос на создание записи в базе данных с помощью метода create, который принимает в качестве первого аргумента хэш со списком пар столбец/значение. Это эквивалентно такому SQL-запросу:
INSERT INTO authors(author) VALUES ($author)
Вызов происходит внутри eval
, поскольку нам может потребоваться перехватить ошибку создания повторного или пустого имени, что вызовет исключение, т.к. в базе данных данный столбец объявлен ненулевым и уникальным.
Функция возвращает хэш с информацией о результатах операции создания записи: успешная или неудачная.
Также можно было бы добавить и операцию удаления, но это влияет на целостность данных, поскольку записи в других таблицах зависят от записей в этой таблице и нам нужно будет принимать решение, что делать с записями в других таблицах при удалении. Базы данных имеют средства для каскадного удаления связанных данных, но в случае форума или блога — это может быть нежелательным явлением. Поэтому на практике обычно заводят дополнительное поле в таблице, которое указывает, что автор удалён или забанен и т.д. Ну а данный пример сразу перестанет быть простым, если реализовывать подобное решение.
Посты
Требуется создавать новые посты, получать один пост или список постов, причём желательно не все стразу, а постранично, т.е. небольшими блоками, а также иметь возможность обновить содержимое поста.
- Один пост по указанному номеру — http-запрос
GET /api/posts/post:postid
- Список постов на заданной странице — http-запрос
GET /api/posts/page:page
- Добавление нового поста — http-запрос
POST /api/posts
- Обновление поста — http-запрос
PUT /api/posts/post:postid
1). Создадим маршрут для извлечения заданного поста:
get '/posts/post:postid' => sub {
my $postid = param('postid');
my $db = Web::Log::DB->db;
my $post = $db->resultset('Post')->find($postid);
{
postid => $post->postid,
postime => $post->postime->datetime,
post => $post->post,
author => $post->authorid->author,
}
};
Для получения одного результата удобно использовать метод find
, который в качестве первого аргумента может принимать значение первичного ключа записи. Возвращается объект Row, представляющий собой полученный ряд таблицы. Как видно, чтобы получить актуальные значения для каждого столбца у данного объекта существуют одноимённые методы-акцессоры (они же будут и мутаторами, если нам захочется изменить значение столбца):
postid
возвращает значение столбцаpostid
postime
возвращает объект классаDateTime
(поэтому для получения строкового представления применяется методdatetime
)post
возращает значение столбцаpost
authorid
возвращает RowAuthor
, поскольку столбец является внешним ключом, и, для получения актуального имени автора, используется вызов акцессораauthor
Использование именованных акцессоров удобно, но иногда требуется просто получить «сырые» данные из базы данных без создания объектов. Такое возможно, если указать в параметрах поиска тип возвращаемого класса ‘DBIx::Class::ResultClass::HashRefInflator’:
my $post =
$db->resultset('Post')
->find( $postid,
{ result_class => 'DBIx::Class::ResultClass::HashRefInflator' } );
В результате будет получена ссылка на хэш, в котором ключи — это названия столбцов, а значения — соответствующие значения столбца.
2). Список постов на заданной странице
get '/posts/page:page' => sub {
my $page = param('page');
my $db = Web::Log::DB->db;
[
$db->resultset('Post')->search(
undef,
{
page => $page,
rows => 10,
result_class => 'DBIx::Class::ResultClass::HashRefInflator'
}
)->all
];
};
Итак, в данном запросе мы используем метод search
для выполнения поиска заданных записей. Нам нужны любые записи, поэтому первый аргумент — фильтр устанавливается в undef
. Атрибут page
задаёт удобный пейджер для выборки записей, начиная с определённой позиции, а параметр rows
определяет сколько записей требуется извлечь. Метод all
выполняет SQL-запрос к базе. Это аналогично подобному SQL-запросу:
SELECT * FROM posts LIMIT $row, ($page-1)*$row
Поскольку в результате такого запроса DBIx::Class
вернёт коллекцию объектов, а нам требуется «сырые» данные в виде массива хэшей, то указывается параметр result_class
со значением DBIx::Class::ResultClass::HashRefInflator
, который выполнит нужную нам операцию и не будет создавать объекты из полученных данных.
Чтобы продемонстрировать возможности DBIx::Class
по созданию сложных запросов, можно несколько усложнить задачу и выдавать не просто список из десяти постов на странице, но и заодно указать, сколько комментариев есть к каждому посту. В этом случае нам придётся воспользоваться операцией объединения JOIN
, чтобы получить выборку, содержащую данные из двух таблиц. Вот так будет выглядеть финальный запрос:
$db->resultset('Post')->search(
undef,
{
join => 'comments',
'+select' => [ { count => 'commentid' } ],
'+as' => [qw(comments_count)],
group_by => ['me.postid'],
page => $page,
rows => 10,
result_class => 'DBIx::Class::ResultClass::HashRefInflator'
}
)->all
Как видно появился параметр join
, который указывает на таблицу, которую мы подключаем в результирующий набор. Поскольку мы определили отношения между таблицами, DBIx::Class
знает по какому столбцу происходит объединение. Параметр +select
определяет, какие дополнительные столбцы будут присутствовать в запросе, в данном случае мы задаём колонку, где будут содержаться результат функции COUNT()
, которая просуммирует количество комментариев для каждого поста. А чтобы нам было удобно ссылаться в полученных результатах на данный столбец, мы задаём этому столбцу псевдоним comments_count
с помощью параметра +as
. Поскольку мы используем агрегирующую функцию, необходимо применить GROUP_BY
, чтобы запустить подсчёт комментариев — это выполняется с помощью параметра group_by => [ 'me.postid' ]
, который будет группировать выборку по столбцу me.postid
(me
– это общепринятый в DBIx::Class
синоним текущей таблицы при формирования SQL-запроса)
Такой код сформирует следующий SQL-запрос:
SELECT me.postid, me.post, me.postime, me.authorid, COUNT( commentid )
FROM posts me LEFT JOIN comments comments ON comments.postid = me.postid
GROUP BY me.postid
LIMIT $row, ($page-1)* $row
3). Для добавления нового поста создадим такой маршрут:
post '/posts' => sub {
my $author = param('author');
my $post = param('post');
my $id;
eval {
my $post_ref = Web::Log::DB->db->resultset('Post')->create(
{
post => $post,
authorid => { author => $author }
}
);
$id = $post_ref->id;
};
{ status => $@ ? 'failure' : 'success', id => $id };
};
В маршрут передаются два параметра: автор и, собственно, сам пост. Поскольку в таблице posts
задаётся не имя автора, а его первичный ключ, то предварительно требуется получение значения этого ключа по имени. Как видно, в DBIx::Class
это реализовано элегантно в виде подзапроса к таблице внешнего ключа:
SELECT authorid, author FROM authors WHERE author= "$author"
INSERT INTO posts ( authorid, post) VALUES ( "$post", "$authorid" )
У данного решения есть побочный эффект: если указанного автора раньше не существовало, то он будет создан. Чтобы избежать этого, можно попробовать действовать по-другому:
my $post_ref =
Web::Log::DB->db->resultset('Author')->find( { author => $author } )
->create_related( posts => { post => $post } );
В данному случае мы сначала ищем автора и затем с помощью метода create_related
создаём в зависимой таблице posts
новый пост. В случае, если автор не найден, то find
вернёт undef
, и метод создания create_related
не выполнится (произойдёт исключение).
Если пост создан успешно, то может быть полезно вернуть информацию о том, какой номер первичного ключа получил данный пост. Для этого используется метод id()
. Теперь фронтенд, зная номер поста, может, например, выполнить переход на его страницу и т.п.
4). Реализуем маршрут для обновления содержимого поста
put '/posts/post:postid' => sub {
my $post = param('post');
my $postid = param('postid');
eval {
Web::Log::DB->db->resultset('Post')->find($postid)
->update( { post => $post } );
};
{ status => $@ ? 'failure' : 'success' };
};
Изменить поле post
можно напрямую через метод update()
, либо предварительно воспользовавшись мутатором post
:
my $p = Web::Log::DB->db->resultset('Post')->find($postid);
$p->post($post);
$p->update();
Комментарии
Требуется создавать комментарии, получать список комментариев для заданного поста и удалять комментарии:
1). Создание комментария
post '/comments/post:postid' => sub {
my $author = param('author');
my $comment = param('comment');
my $postid = param('postid');
my $id;
eval {
$id =
Web::Log::DB->db->resultset('Author')
->find( { author => $author } )
->create_related(
comments => {
{
comment => $comment,
postid => $postid,
}
}
)->id();
};
{ status => $@ ? 'failure' : 'success', id => $id };
};
2). Получение комментариев к посту
get '/comments/post:postid' => sub {
my $postid = param('postid');
[
Web::Log::DB->db->resultset('Comment')->search(
{ postid => $postid },
{
order_by => 'commentid',
result_class => 'DBIx::Class::ResultClass::HashRefInflator'
}
)->all
];
};
3). Удаление комментария
del '/comments/comment:commentid' => sub {
my $commentid = param('commentid');
eval {
Web::Log::DB->db->resultset('Comment')
->find($commentid)->delete();
};
{ status => $@ ? 'failure' : 'success' };
};
Запуск приложения
Полученное API бэкенда реализует так называемые CRUD-операции (Create, Read, Update, Delete — Создание, Чтение, Обновление, Удаление) над базой данных. Основной же класс веб-приложения будет выглядеть так:
package Web::Log;
use Dancer ':syntax';
use Web::Log::API;
our $VERSION = '0.1';
prefix undef;
set content_type => 'text/html';
get '/' => sub {
template 'index';
};
true;
Здесь подключается модуль API — Web::Log::API
и задаётся маршрут корня веб-приложения. Запускается веб-приложение с помощью команды
$ perl -Ilib bin/app.pl
Таким образом, работа над бэкендом завершена, отображение данных может быть реализовано на фронтенде с помощью различных фреймворков (например, Backbone.js), использующих API бэкенда для извлечения и отображения необходимых данных.
Заключение
В статье был рассмотрен пример создания бэкенда веб-приложения, использующего DBIx::Class
для работы с базой данных через объектный интерфейс. Разъяснены ключевые понятия Schema, ResultSet и Row, а также типичные CRUD-операции с помощью объектных методов DBIx::Class
. По понятным причинам этот обзор не может охватить весь спектр возможностей DBIx::Class
, поэтому рекомендуется за дополнительной информацией всегда обращаться к первоисточнику — документации модуля DBIx::Class
. Также действует почтовый список рассылки и irc-канал #dbix-class
на сервере irc.perl.org, где можно смело задавать интересующие вопросы.
← Подготовка к YAPC::Europe 2013 | Содержание | Секретные операторы Perl и не только →