この記事を Movable Type Advent Calendar 2012 の 20 日目に贈ります。
MovableType の管理画面は Perl で書かれた CGI として動作しており、ウェブサイトやブログ記事などなど、様々なデータはその後ろにあるデータベースに格納されています。また、ダイナミック パブリッシング機能については PHP で書かかれていますので、MovableType において Perl および PHP によるデータベースへのアクセス手段が標準で提供されています。そのデータベースのテーブル構造も難しいものではありませんから、Perl、PHP 以外の他のプログラミング言語からも、MovableType のデータを容易に利用することができます。そこで今回、Ruby を用いてゴニョゴニョしてみました。
データベース上のデータ レコードを操作する際、一般的には SQL という言語が用いられます。SQL を用いれば、データに対してありとあらゆる操作が可能ですが、MovableType などのような実際のアプリケーションでは、そこまでプリミティブなデータ操作が必要なかったり、また、アプリケーション側で SQL を直接記述すると、可読性や保守性が著しく悪くなるなどの問題があります。
そこで、アプリケーション プログラムからデータベースを操作する際には、O/R マッパー という仕組みを利用することがほとんどです。データベースに格納されたデータ レコードを、プログラム内からはあたかもオブジェクトの様に扱うことができるようになります*1。実際にデータベースにデータを格納したり、データを取得する処理は SQL によって行われるのですが、煩雑になりがちなこれらの手続きは、O/R マッパーが裏側で頑張ってくれるため、プログラムは SQL を全く意識する必要がなくなります。簡単な具体例を見てみましょう。
$data = $dbh->selectrow_hashref('SELECT * FROM `mt_entry` LIMIT 1') or die 'No Data'; print $data->{entry_title};
$entry = MT::Entry->load() or die 'No Data'; print $entry->title;
$r = $dbh->do('SELECT * FROM `mt_entry` WHERE `entry_id` = 5'); $r == 0 # レコードが無かったら INSERT ? $dbh->do('INSERT INTO `mt_entry` (`entry_id`, `entry_title`) VALUES (5, \'まそっぷ!\')') # 存在したら UPDATE : $dbh->do('UPDATE `mt_entry` SET `entry_title` = \'まそっぷ!\' WHERE `entry_id` = 5');
$entry = MT::Entry->new; $entry->id(5); $entry->title('まそっぷ!'); $entry->save;
コードの詳細な説明は省きますが、O/R マッパを利用したプログラムの方が、動作の意図を感覚的に簡潔に読むことができると思います。上記の短いサンプル コード程度ではあまり実感できませんが、データベース上のテーブル名やカラム名などの元のアイデンティファイアを、オブジェクト内に隠蔽することができるため、プログラムを簡潔に記述できることの他にも、データベース製品ごとの SQL 文法の違いを吸収してくれるなど、O/R マッパには様々なメリットがあります。
Ruby では ActiveRecord ライブラリを利用することで、簡単にこれを実現できます。
# 必要なライブラリを読込む require "rubygems" require "active_record" # データベースに接続 ActiveRecord::Base.establish_connection( :adapter => 'mysql', :host => 'localhost', :username => 'root', :password => 'root', :database => 'mt_test51' ) # 命名規則を調整 ActiveRecord::Base.pluralize_table_names = false ActiveRecord::Base.table_name_prefix = "mt_" ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore # ブログ記事オブジェクトを定義 class Entry < ActiveRecord::Base end # 一番最初のブログ記事を取得 entry = Blog.find(:first) puts(entry.entry_title) # タイトルを書き換えて保存 entry.entry_title='まそっぷ!' entry.save
ActiveRecord を用いて MovableType のテーブルにアクセスする際に注意する点があります。"設定より規約"を重んじる ActiveRecord では—— Ruby on Rails でもそうですが——、規約に従う部分については、開発者がわざわざそれを明示的に指定する必要がありません。20 行目の Entry
クラスは規約に従って勝手に(?) entries
テーブルに接続されますし、25 行目の entry_title
メソッドは同じく規約に従って勝手に entries
テーブルの entry_title
カラムへのアクセサとして定義されます。
これは開発者が、Entry
クラスを Recipe
テーブルにわざわざ接続したり、entry_title
を hogehoge
カラムに当て嵌めたりする設計の方がむしろ例外的なケースであって、そのような"至極当たり前な"テーブル名やカラム名を開発者に考えさせたり、その稀有な例外のために定型的な設定を開発者にいちいち強いたりするくらいならば、『「クラス名=テーブル名、アクセサ名=カラム名」という規約に従って動作するぞ!』としてしまった方が、開発者にとってもハッピーだということです。そして、その規約から外れる場合にのみ、設定を行わせればよい。これはなかなか面白い考え方です。
さて、実際に MovableType のテーブル設計は ActiveRecord の規約に則っていませんので、その例外を設定する必要があります。具体的に見てみましょう。
pluralize_table_names
... ActiveRecord のクラスが接続されるテーブル名は、そのクラス名から決定され、例えば、Product
クラスは標準で products
テーブルに接続されます。このフラグはテーブル名を複数形にしません。MovableType の場合、Entry
クラスは mt_entry
テーブルに格納されますので、false(複数形に変換しない)を指定します。table_name_prefix
... ActiveRecord のクラスが接続されるテーブル名の接頭句を定義します。MovableType の場合、Entry
クラスは mt_entry
テーブルに格納されるので、mt_ を指定します。primary_key_prefix_type
... ActiveRecord のレコードは主キーとして標準で id
が定義されます。このフラグは、主キーにテーブル名の接頭句を追加します。MovableType の場合、Entry
クラスの主キーは entry_id
なので、この指定が必要です。このあたりの解説は Rubyist Magazine - RubyOnRails を使ってみる 【第 3 回】 ActiveRecord が詳しいです。
先のソースコードの 25 行目、28 行目に違和感がありませんでしょうか? Entry
オブジェクトにタイトルを取得/設定している部分で、entry
という文字列が重複しています。これは ActiveRecord の規約に因る部分でもありますが、そもそも MovableType のテーブルの命名規則って変かもしれません。
まず、各テーブル名ですが、MovableType が利用するデータベース内に存在するテーブルは、基本的かつ当たり前には MovableType が利用するものなので、テーブル名の接頭句である mt_
は不要な気がしないでもありません。仮に MovableType のデータベースを MovableType 以外のシステムと共用することがあっても、それは例外的なことであって、接頭句が標準で必要な理由にはなり得ません。恐らくは最初の設計がそうであったのと*2、バージョンアップでデータベースを引き継げる必要性から、そのままズルズルと来てしまったんだと思いますケド...
次に、テーブル内のカラム名についても同じことが言えます。mt_entry
のタイトルを表すカラムは title
であれば十分なのに、なぜか entry_title
と定義されています。これが先の 25 行目、28 行目の違和感の理由です。そのため、mt_permission
テーブルには permission_permissions
カラムがあったりします(´・ω・`)くどいよ!
そんでもって、んじゃ、このくどい名前はどこで生成しているんだろう? ということを調べると...
sub db_column_name { $_[2] }
sub db_column_name { my $dbd = shift; my ( $table, $col ) = @_; return $1 if $col =~ m/\!(.*)\!/; $table =~ s{ \A mt_ }{}xms; return join( '_', $table, $col ); }
おまえかー! ということで、オーバーライドしている db_column_name
をコメントアウトしてやれば、素直な(?)カラム名でアクセスできるようになります。ざっと見た範囲で、普通に使っている範囲内では、MT::Object
の後ろ側に手を突っ込んでカラム名を直接指定するようなコードは見つからなかったので動作すると思います。ただし、データベースに直接アクセスするようなプラグインや拡張は、作り様にも拠りますがたぶん動作しません。
...と言いますか、このお手軽ハックだけでは初期インストールとアップグレードが動作しない大問題があります。というのも、初期インストールやアップグレードの際に、テーブルやカラムを作ったりする処理において、カラム名を上記の db_column_name
メソッドを使わずに自前で生成するようハードコーディングされている部分が散在しておりまして(´・ω・`) そう簡単には直せませんし、影響範囲もちょっとアレな感じです。例えば;
return $field_prefix . '_' . $name . ' ' . $type . $nullable . $default . $key;
sub column_defs { ... my $field_prefix = $class->datasource; ... next if $colname !~ m/^\Q$field_prefix\E_/i; $colname =~ s/^\Q$field_prefix\E_//i;
これらの部分を目grepで探してゴリゴリと修正したところ、何とか初期インストールも成功し、スマートなカラム名が生成されました。 ...という訳で、労力の割りにあんまり需要が無さそうですが、このあたりの動作も行儀良くして頂けると、他言語プラットホームへの展開もし易くなるのになぁ...と思うのでした。
今回、Ruby の ActiveRecord に加えて、Java の Hibernate についても書くつもりだったんですが、環境作るのメンドクセーってなって書けませんでした( ´∀`)σ)Д`) MovableType のコンテンツ出力方向への hack も面白いですが、MovableType を/が司る根源であるデータベースを基点にして他方面に展開してみるのも面白いかもしれません。おしまい。