Sunday, September 9, 2012

6 полезных приемов для работы с DBIx::Class

DBIx::Class - это мощный ORM, написанный на Perl. Его функцией является прозрачное преобразование результатов запросов к реляционной БД в перловые объекты. Разработчик может использовать эти объекты для взаимодействия с БД, не прибегая к написанию SQL. Это значительно ускоряет разработку и делает код более простым для понимания. К достоинствам DBIx::Class также относятся потрясающая гибкость и интуитивность использования (DWIM). В этой заметке я приведу несколько приемов, которые помогут вам использовать DBIx::Class более эффективно.


  1. Автоматическая генерация классов с помощью DBIx::Class::Schema::Loader.


    Как известно, прежде чем начать работу с DBIx::Class, необходимо предварительно создать несколько классов: класс схемы и Result-классы для нужных таблиц. Этот шаг можно автоматизировать, и поможет в этом утилита dbicdump, которая устанавливается вместе с вышеуказанным модулем. Она берет информацию о таблицах из БД (столбцы, ключи, связи, etc.) и создает нужные классы для всех таблиц.
    dbicdump -o dump_directory=lib 
    MyApp::Schema dbi:Pg:dbname=myapp user pass

    Теперь можно использовать сгенерированные модули в приложении.
    При внесении каких-либо изменений в схему БД, классы необходимо пересоздать аналогичным образом. Для удобства можно создать shell-скрипт или alias, вызывающий dbicdump с нужными параметрами.


  2. Объединение нескольких таблиц в одном запросе с помощью join и prefetch.


    Если в схеме БД созданы вторичные ключи, и вы воспользовались приемом №1, можно использовать атрибуты join и prefetch во многих методах DBIx::Class для объединения нескольких таблиц в одном запросе. join используется когда необходимо выполнить поиск либо сортировку результатов по одному или более столбцу связанной таблицы, e.g.
    my $rs = $schema->resultset('CD')->search(
    { 'artist.name' => 'Rainbow' },
    { join => 'artist' }
    );

    Хотя join и объединяет несколько таблиц, в полученных объектах содержатся данные лишь из одной таблицы - cds. Т.е. если вызвать $cd->artist, будет сделан дополнительный запрос к БД. Для того, чтобы получить все данные из связанных таблиц одним запросом, вместо join следует использовать prefetch. И join, и prefetch могут иметь произвольный уровень вложенности.


  3. Добавление методов в Result/ResultSet-классы.


    Мы уже знаем, что в DBIx::Class для каждой таблицы должен быть задан Result-класс, который представляет строку из данной таблицы. В этот класс можно добавлять дополнительные методы для работы со строками. Например, если в таблице есть столбец email, можно добавить метод gravatar_url:
    sub gravatar_url {
    my ($self) = @_;
    return "http://www.gravatar.com/avatar/".md5_hex(lc $self->email);
    }

    В ResultSet-классы в свою очередь добавляются методы для работы с наборами строк: поиск с заданными критериями, сортировкой, etc. Это позволяет инкапсулировать часть логики в модели.


  4. Использование компонентов для добавления функционала в базовые классы DBIx::Class.


    Функционал любого класса в DBIx::Class (e.g. Schema, Result) можно расширить с помощью компонента:
    __PACKAGE__->load_components(qw(MyComponent));

    При этом MyComponent добавляется в цепочку наследования класса и может добавлять новые методы в класс, либо переопределять уже имеющиеся. Например, компонент DBIx::Class::Validation переопределяет методы insert и update в Result-классе и добавляет в них валидацию значений.


  5. Использование чистого SQL с параметрами.


    В некоторых случаях нужный SQL-запрос в DBIx::CLass можно сгенерировать лишь с использованием кастомного SQL. Данный подход используется в крайнем случае, и, как правило, можно обойтись и без этого. Чтобы включить строку SQL в сгенерированный запрос, она передается как ссылка на скаляр (подробный синтаксис можно посмотреть в perldoc SQL::Abstract). Несколько примеров:
    #  WHERE YEAR(birthday) = ?
    $rs->search(['YEAR(birthday) = ?', [dummy => $year]]);

    # ORDER BY myfunction(mycol, ?, ?) DESC
    $rs->search(undef, {
    order_by => [
    'myfunction(mycol, ?, ?) DESC', map [dummy => $_], $v1, $v2
    ]
    });

    # SET price = price + ?
    $rs->update({
    price => ['price + ?', [dummy => $v]],
    });



  6. Использование транзакций.


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

    DBIx::class позволяет выполнить любой код в транзакции с помощью метода txn_do класса схемы:
    $schema->txn_do(sub {
    my $user = $schema->resultset('User')->create({
    username => $username,
    first_name => $first_name,
    last_name => $last_name,
    });
    $schema->resultset('Token')->validate($token_id) or die "Bad token: $token_id";
    $user->add_to_tokens({ token_id => $token_id });
    });



No comments:

Post a Comment