Как определить и использовать отношения many_to_many в DBIx::Class?

У меня есть 3 таблицы в БД, упрощенно как то:

book        book_language       language
=====  <->> ============== <<-> ========
bookID      book_languageID     languageID
title       bookID              language
            languageID

С DBIx::Class::Schema::Loader Я сгенерировал схему, где соответствующие классы Result:

Book
BookLanguage
Language

По ряду причин Loader не обнаружил отношения many_to_many между этими таблицами, поэтому я сам определил отношения следующим образом: Language учебный класс:

package R::RMT::Result::Language;
...
__PACKAGE__->many_to_many('books' => 'book_language_rel', 'bookid_rel');

И в Book учебный класс:

package R::RMT::Result::Book;
...
__PACKAGE__->many_to_many('languages' => 'book_language_rel', 'languageid_rel');

Теперь я надеялся получить доступ ко всем родственным языкам, как это:

my $dsn = "DBI:mysql:database=rkBook";
my $schema = R::RMT->connect( $dsn, 'user', 'pwd' );

my $book_rs = $schema->resultset('Book');
say $book_rs->languages();

Но я получил ошибку:

Can't locate object method "languages" via package "DBIx::Class::ResultSet" at ...

Что я не так понял? Я пытался связать воедино фрагменты из документов, но, очевидно, что-то не так. Я никогда не видел ни одного полного примера того, как many_to_many отношения должны работать.

AFAIU, определяющий отношения в классе Result, должен сделать метод доступа в этом классе. Как я мог видеть все созданные методы доступа? Если я пытаюсь сбросить объект ResultSet с Data::Printer Я вижу только аксессоры для столбцов, но нет аксессоров для отношений.

Если я попытаюсь перечислить отношения с:

say $schema->source('Book')->relationships; 

Я не вижу здесь many_to_many отношения (а не те, которые подобраны DBIx::Class::Schema::Loader), только has_many с и belongs_to s.

Редактировать. Добавлен простейший тестовый пример:

Создать таблицы и заполнить данными

CREATE TABLE `book` (
  `bookID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8_estonian_ci NOT NULL DEFAULT '',
  PRIMARY KEY (`bookID`),
  KEY `title` (`title`)
) ENGINE=InnoDB;

CREATE TABLE `language` (
  `languageID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `language` varchar(255) COLLATE utf8_estonian_ci NOT NULL DEFAULT '',
  PRIMARY KEY (`languageID`),
  KEY `language` (`language`)
) ENGINE=InnoDB;

CREATE TABLE `book_language` (
  `book_languageID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `bookID` int(10) unsigned DEFAULT NULL,
  `languageID` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`book_languageID`),
  UNIQUE KEY `book_language` (`bookID`,`languageID`),
  CONSTRAINT `book_language_ibfk_1` FOREIGN KEY (`languageID`) REFERENCES `language` (`languageID`) ON DELETE SET NULL,
  CONSTRAINT `book_language_ibfk_2` FOREIGN KEY (`bookID`) REFERENCES `book` (`bookID`) ON DELETE SET NULL
) ENGINE=InnoDB;

INSERT INTO language (language) VALUES ('estonian'), ('english'), ('polish');
INSERT INTO book (title) VALUES ('Eesti rahva ennemuistsed jutud'), ('Estonska-polska slovar'), ('21 facts about...'), ('Englis-Polish Dictionary');
INSERT INTO book_language (bookID, languageID) VALUES (1,1), (2,1), (2,3),(3,1),(3,2),(3,3),(4,2),(4,3);

Создать схему со значениями по умолчанию:

dbicdump -o dump_directory=./lib -o debug=1 My::Schema 'dbi:mysql:dbname=testbook' user password

добавленной many_to_many -определения в Book.pm

# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-03-21 18:49:05
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ipamXRkSe+HLXGdTGwzQ9w
__PACKAGE__->many_to_many('languages' => 'book_languages', 'languageid');

И в Language.pm

# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-03-21 18:49:05
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nZyaWdriRpgEWDAcO3+CFw
__PACKAGE__->many_to_many('books' => 'book_languages', 'bookid');

Запустите этот скрипт:

#!/usr/bin/env perl

use strict; use warnings; use 5.014; use utf8::all;
use My::Schema;

my $dsn = "DBI:mysql:database=testbook";
my $schema = My::Schema->connect( $dsn, 'user', 'password' );

my $book_rs = $schema->resultset('Book');
say $book_rs->languages();

2 ответа

Решение

Прежде всего обратите внимание, что many_to_many генерирует только вспомогательные методы (методы доступа), но не являются "реальными" отношениями в том смысле, что вы не можете использовать их в параметрах join и prefetch. Проблема с вашим примером кода состоит в том, что вы пытаетесь вызвать метод результата (сгенерированные "языки") для объекта набора результатов. Например, вызов $rs->first->languages ​​будет работать. Если вы хотите, чтобы все языки всех книг были в ваших $book_rs, вы должны использовать search_related для двух отношений, образующих many_to_many. Это не затронет базу данных, если вы предварительно выберете два или не извлечете rs прежде, и в этом случае будет создан и выполнен оптимизированный запрос.

Отношения многие ко многим основываются на существующих отношениях has_many и own_to. Если они не настроены правильно, отношения many_to_many также не будут работать. В вашей ситуации я ожидаю, что DBIC::Schema::Loader создаст следующие отношения:

В Book.pm

__PACKAGE__->has_many( 'book_languages'
                       'Your::DBIC::Schema::BookLanguages',
                       'bookID' );

В Language.pm

__PACKAGE__->has_many( 'book_languages'
                       'Your::DBIC::Schema::BookLanguages',
                       'languageID' );

В BookLanguage.pm

__PACKAGE__->belongs_to( 'book'.,
                         'Your::DBIC::Schema::Book' );
__PACKAGE__->belongs_to( 'language',
                         'Your::DBIC::Schema::Language' );

Затем вы можете вручную добавить следующее:

В Book.pm

__PACKAGE->many_to_many( 'languages',
                         'book_languages',
                         'language' );

В Language.pm

__PACKAGE->many_to_many( 'books',
                         'book_languages',
                         'book' );
Другие вопросы по тегам