DBIC で後付けの relationship を定義してたらうまく動かなくなった
昔かいた DBIC を使うコードが,DBIC を新しくしたら動かなくなった。
うまくいく例
use strict; use warnings; # artist の定義 package My::Schema::Result::Artist; use base qw( DBIx::Class ); __PACKAGE__->load_components(qw( Core )); __PACKAGE__->table('artist'); __PACKAGE__->add_columns(qw( id name )); __PACKAGE__->set_primary_key('id'); # album の定義 package My::Schema::Result::Album; use base qw( DBIx::Class ); __PACKAGE__->load_components(qw( Core )); __PACKAGE__->table('album'); __PACKAGE__->add_columns(qw( id title artist_id )); __PACKAGE__->set_primary_key('id'); # relationship の定義 My::Schema::Result::Artist ->has_many('albums', 'My::Schema::Result::Album', 'artist_id'); My::Schema::Result::Album ->belongs_to('artist', 'My::Schema::Result::Artist', 'artist_id'); # スキーマの定義 package My::Schema; use base qw( DBIx::Class::Schema ); __PACKAGE__->register_class('artist', 'My::Schema::Result::Artist'); __PACKAGE__->register_class('album', 'My::Schema::Result::Album'); # メイン package main; my $schema = My::Schema->connect('dbi:SQLite:test.db'); my @artists = $schema->resultset('artist')->search( { # conditions }, { prefetch => 'albums', }, )->all();
全体スキーマ定義に各テーブルスキーマを導入するために register_class() を使っているけれど,普通は load_namespaces() か load_classes() を使うんだと思う。あるいは DBIx::Class::Schema::Loader を使ってクラス上でスキーマを定義するんじゃなくて DB からスキーマ定義を自動生成するほうがイマドキかもしれない。
が,ちょっと(後述するような)事情があって上記のように register_class() を使ってる。
実行すると,
$ DBIC_TRACE=1 perl test.pl SELECT me.id, me.name, albums.id, albums.title, albums.artist_id FROM artist me LEFT JOIN album albums ON albums.artist_id = me.id ORDER BY albums.artist_id:
うまくいってる。
うまくいかない例
んが,古いコードでは下記のようになっていた。
# artist の定義 package My::Schema::Result::Artist; # ...... snip snip snip ...... # # album の定義 package My::Schema::Result::Album; # ...... snip snip snip ...... # # スキーマの定義 package My::Schema; use base qw( DBIx::Class::Schema ); __PACKAGE__->register_class('artist', 'My::Schema::Result::Artist'); __PACKAGE__->register_class('album', 'My::Schema::Result::Album'); # メイン package main; # relationship の定義 My::Schema::Result::Artist ->has_many('albums', 'My::Schema::Result::Album', 'artist_id'); My::Schema::Result::Album ->belongs_to('artist', 'My::Schema::Result::Artist', 'artist_id'); # ...... snip snip snip ...... #
さきほどの例との違いは,全体スキーマに register_class() したあとに各テーブルスキーマの relationship を定義しているところ。
ふつうの感覚ではこれらのコードの働きにさほど違いがないように思えるが,さいきんの DBIC で実行すると,
No such relationship albums at /usr/lib/perl5/site_perl/5.8.8/DBIx/Class/Schema.pm line 1027
のように怒られる(古い DBIC では大丈夫だった)。
なんでこのようなコードレイアウトになってたかというと,当該アプリケーションでは,スキーマ定義にまつわる部分(冒頭〜メインまで)を SQL-Translator で SQL 文から自動生成していたから。
自動生成+カスタムの relationship(実際には別モジュールとしている)というしくみになっているので,上記の「うまくいく」例のようにコードの並び順を変えることは望ましくない。スキーマを変えるたびに手で変えることになるから(もちろんそこを含めて自動生成するようなスクリプトを書いてもいいんだけど)。
うごかすには
深追いする体力も能力もないので,アドホックに解決できないかといろいろ試行錯誤してたら,下記のようにすると一応動くようになった。
# artist の定義 package My::Schema::Result::Artist; # album の定義 package My::Schema::Result::Album; # スキーマの定義 package My::Schema; use base qw( DBIx::Class::Schema ); __PACKAGE__->register_class('artist', 'My::Schema::Result::Artist'); __PACKAGE__->register_class('album', 'My::Schema::Result::Album'); # メイン package main; # relationship の定義 My::Schema::Result::Artist ->has_many('albums', 'My::Schema::Result::Album', 'artist_id'); My::Schema::Result::Album ->belongs_to('artist', 'My::Schema::Result::Artist', 'artist_id'); ### もっかい register_class する My::Schema->register_class('artist', 'My::Schema::Result::Artist'); My::Schema->register_class('album', 'My::Schema::Result::Album'); # ...... snip snip snip ...... #
なんのことはない,relationship の設定をおこなったあとに,再度全体スキーマに register_class() しなおしているだけ。
副作用があったらイヤだけど,とりあえずこれで動いてる。もっとよい案があったら教えてください。
いちいち register_class() を列挙するのが面倒なら
### もっかい register_class する if (eval { My::Schema->can('class_mappings') }) { my $mappings = My::Schema->class_mappings; while (my ($class, $rs) = each %$mappings) { My::Schema->register_class($rs, $class); } }
みたいに,My::Schema に登録されているスキーマ定義クラスリストをもとに自動的に register_class() を実行するように書くこともできる。でも非公開 API を使っているので今後うまく動かなくなる可能性もある。