ダック・タイピング

データベースのテーブルはオブジェクトじゃないので、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のダック・タイピングは素敵。