2018年11月05日
Listing Frameworkで別のオブジェクトのカラムの列を追加する
PowerCMS をカスタマイズして何らかのデータを管理できるようにする場合、記事に関連付けたオブジェクトを作成することがあると思います。そのような場合、そのオブジェクトの情報が記事一覧に表示されるとより便利になります。ここではそのやり方をご紹介します。
表示するオブジェクトとして、以下のような EntryChild クラスと、記事側にその ID を格納する entry_child_id というものがあるとします。EntryChild クラスの「name」プロパティを記事一覧に表示させたいと思います。
object_types: entry_child: EntryChild::EntryChild entry: entry_child_id: integer not null
package EntryChild::EntryChild; use strict; use base qw( MT::Object ); __PACKAGE__->install_properties( { column_defs => { 'id' => 'integer not null auto_increment', 'name' => { 'label' => 'Name1', 'type' => 'string', 'size' => 255, }, }, datasource => 'entry_child', primary_key => 'id', } ); 1;
list_properties の設定
値の表示、ソート、フィルタのそれぞれで独自の処理が必要になるため、html、sort、termsのハンドラを実装する必要があります。したがって list_properties の設定は以下のようになります。
list_properties: entry: entry_child_name: label: Entry child name order: 1000 display: force base: __virtual.string html: $entrychild::EntryChild::Plugin::list_prop_html sort: $entrychild::EntryChild::Plugin::list_prop_sort terms: $entrychild::EntryChild::Plugin::list_prop_terms
html ハンドラ
まず、html ハンドラは以下のように単純に ID に該当するオブジェクトを取得して name プロパティを返すだけで実装できます。ただ、この方法だとリストの行数だけクエリを発行してしまうため、bulk_html を使用して一括でオブジェクトを取得するようにすることで高速化することができます。
sub list_prop_html { my ($prop, $entry) = @_; my $child = MT->model('entry_child')->load({ id => $entry->entry_child_id }); return $child ? $child->name : ''; }
sort ハンドラ
sort ハンドラの実装にはテーブルの JOIN が必要になります。また、INNER JOIN では子オブジェクトを持たない記事がリストに表示されなくなってしまうため、LEFT OUTER JOIN で行う必要があります。JOIN の指定は MT::Objectの join_on( JOIN_COLUMN, $terms, $args ) メソッドで生成することができ、今回使用する JOIN の指定は以下のコードで生成することができます。
MT->model('entry_child')->join_on( undef, undef, { type => 'LEFT OUTER', condition => { 'id' => \'= entry_entry_child_id' } }, );
JOIN の種類は引数 $args の type キーで指定することができます。ここに 「LEFT OUTER」を指定することで LEFT OUTER JOIN を行うことができます。join_on() はテーブルの結合に使用するカラムとして親テーブルの主キーと子テーブルの JOIN_COLUMN を使用することを想定していますが、今回は逆の関係になっている引数 $args の condition キーでJOINの条件を指定しています。ハッシュのキーが子テーブル側のカラムになります。値の方は絞り込み使われる値となりますが、スカラのリファレンスになっている場合は、その値がそのまま SQL に組み込まれることを利用して親テーブルのカラムを指定しています。
JOIN の情報は sort と terms のハンドラの引数 $args に設定しますが、これらのハンドラは同時に呼ばれる可能性があるため、各ハンドラで個別に設定すると重複してしまう可能性があります。そのため、$args に JOIN の情報が無かったら追加してそれを返す関数 _join_entry_child() を作成しておきます。
sub _join_entry_child { my ($args) = @_; my $app = MT->instance; my $class = $app->model('entry_child'); my ($join) = grep $_->[0] eq $class, @{$args->{joins}}; unless ($join) { $join = $class->join_on( undef, undef, { type => 'LEFT OUTER', condition => { 'id' => \'= entry_entry_child_id' } }, ); $args->{joins} ||= []; push(@{$args->{joins}}, $join); } return $join; }
以上を踏まえ、sort ハンドラの実装は以下のようになります。
sub list_prop_sort { my ($prop, $terms, $args) = @_; my $join = _join_entry_child($args); $join->[3]->{sort} = 'name'; $join->[3]->{direction} = $args->{direction}; delete $args->{sort}; delete $args->{direction}; return; }
まず _join_entry_child() で JOIN の情報を追加します。変数 $join は join_on() の戻り値で [ $class, JOIN_COLUMN, $terms, $args ]
という配列のリファレンスになっています。JOIN を用いたソートの指定は $args に指定するため4番目の要素にそれを追加します。通常の場合にソートの指定を行う引数 $args にはデフォルトのソート情報が設定されているため、それを削除しておく必要があります。
terms ハンドラ
同様に terms ハンドラの実装は以下のようになります。本来であれば「を含む」「を含まない」などの指定に対応する必要がありますが、ここではその実装を省いています。
sub list_prop_terms { my ($prop, $args, $db_terms, $db_args) = @_; my $value = $args->{'string'}; return unless $value; my $join = _join_entry_child($db_args); $join->[2] ||= []; push(@{$join->[2]}, { 'name' => { like => "%$value%" } }); }
sort ハンドラと同様に _join_entry_child() で JOIN の情報を追加します。JOIN を用いた検索条件の指定は変数 $join の $terms に指定するため、3番目の要素にそれを追加します。
以上で、記事一覧に EntryChild オブジェクトの name プロパティのカラムが追加され、ソートやフィルタが使用できる状態になります。
コメントを投稿する