ダック・タイピング
データベースのテーブルはオブジェクトじゃないので、OOなコードとはあんまり合わない。ActiveRecord(というかRails全般)はこの辺りをダック・タイピングでうまいこと繋げてくれる。(適切な例かちょっと自信ないが)いろんなデータを順序付きで管理する構造(リビジョン管理付き)を作るとすると、
CREATE TABLE HOGES ( id integer not null primary key, title string not null ); CREATE TABLE FUGAS ( id integer not null primary key, ident_string string not null ); CREATE TABLE HOGE_DATA_REVISIONS ( id integer not null primary key, hoge_id integer not null, any_data_group_id integer not null, sort_order integer not null ); CREATE TABLE FUGA_DATA_REVISIONS ( id integer not null primary key, fuga_id integer not null, any_data_group_id integer not null, sort_order integer not null ); CREATE TABLE ANY_DATA_GROUPS ( id integer not null primary key, name string not null );
と、OOとかあまり意識しないで普通に正規化したDBを作っておいてから、
class Hoge < ActiveRecord::Base alias :name :title end class Fuga < ActiveRecord::Base alias :name :ident_string end class HogeDataRevision < ActiveRecord::Base belongs_to :hoge belongs_to :any_data_group alias :item :hoge end class FugaDataRevision < ActiveRecord::Base belongs_to :fuga belongs_to :any_data_group alias :item :fuga end class AnyDataGroup < ActiveRecord::Base has_many :hoge_data_revisions has_many :fuga_data_revisions def members hoge_data_revisions + fuga_data_revisions #sort_order で並べ替えして返すコードをココに書く。 end end
みたいにモデル側でズルズル取りまとめておくと
group = AnyDataGroup.find(1) puts "==== #{group.name} ====" group.members.each do |member| puts member.item.name end
というふうに使えて便利。「:dependent => :destroy」とか「:through」と組み合わせると、もっと多段の連鎖になってもcreateからdestroyまでの処理がスッキリまとまる。
テーブル設計でポリモーフィズムを頑張る方法もあると思うのだけれど、それってなんだか大変。プログラムのためにデータの持ち方を変えるのは良くないこと。
だから「DBはDBらしく」「コードはコードらしく」が簡単に実現できるActiveRecordの作りとRubyのダック・タイピングは素敵。