« 本社に戻ったら席がどこにもなかったこの仕打ち。 | トップページ | 良さ気なものをいくつかメモ。 »

Ruby on Railsでデータの抽象化をしてみる。

まー、仕事で必要だったので。あと、何度か挑戦してはハマっていたので、後々のために考え方を記録。

だいたい実現できそうな感触にはなったのだけど、はたしてこれは規約違反なのかどうかのか…。コードに無理はないと感じているので、たぶん許容範囲とは思うけど…。

しかしRonRはつくづく複合キーに弱いな。

目的:

異なるModelで表現すべきデータを、相互に関連付ける。つまりはハイパーリンクというか、RDBの構造そのものというか。

作っているものとしては、研究プロジェクトとその成果と報告書をかきあつめて関連を示す、というようなモノ(どこでみょんな問題に巻き込まれるかわからんのでソノモノは書かないけど、これだけ書けば十分な気も)。

問題:

  • 同種Model同士なら2点の関係を示すリストを作り、belongs_toで各々を示してやれば終了…なのだけど、異種Modelになると:class_nameに指定する内容がデータ依存で変わってしまい、コードベースで定義できない。
  • 同種Model同士のリストをModel数作ればいいのだけど、関係を示す部分のコードがほとんど同じになることが確実で、非常にうざい。てか、RonRの原則にも反していると思うし。
  • 同種Model同士のリストを作っていくと、テーブルもその分増えていくので嫌。
    なので、共通のテーブルを1つ作り、そこに「どのModelのデータか」を記録することにした。

駄目だったこと:

  1. Moduleでベース部分作って、その中にbelongs_to書いてやれば、includeした先のクラスで展開されないかな…。
    →C/C++の#include的に動いてもらえればベストだったのですが、Moduleでもコード自体は解釈されてからincludeが発動するのですね…。
  2. ベースクラス作って、そこにbelongs_to書いて、
    :class_name => self.name
    みたいにすれば継承先のサブクラスでselfの部分を動的に解釈してもらえないかな…。
    →selfはベースクラスになっちゃいますな…。

今試していること:

  • belongs_toはサブクラス(実際にModel同士を連結するクラス)に記述する。その代り、コピペで済むように書式は全部統一し、:class_nameは
    :class_name => self.name
    にする。これで、車輪の再生産も最低限の労力で済む。
  • ベースクラスは、共通使用するテーブルの名前定義と、コーディング支援用の共通処理を書く場所にする。
  • テーブルに「どのMolelのデータか」を示す情報を書くところは、ベースクラスでダミーのメソッドを用意し、ベースクラスに実装したbefore_save()で呼び出す。
    あと、今使っているRailsがsave_before/afterが無い時代のもの(汗)なので、save()をオーバーライドしてこいつらを呼びだすところもベースに実装。これで、Railsをアップデートしたときに修正するところも少なくて済む。
    [2010.01.18 更新; よくよく見たらbefore/after_save()だった。動かないわけだよ…]
    (Railsもアップデートしないといけないんだけど、それやると動かなくなるコードがちらほらあって…(汗))

で、ざっくり以下のような感じ。手元には当然ソースなんて持ってきていないので、記憶から再コーディングですがー。なので細かいところははしょっているし、多少のミスは見なかったことで。

class Relation < ActionRecord::Base

  # 同一Modelごとに順序を保ちたいので、連番をつけてます。その最大値をとる。
  def max_relation_order
    max_rec = Relations.find( :first :condition => {
        :source_id => self.sorce_id,
        :related_id => self.related_id,
        :relation_class => self.relation_class    # これが格納しているデータのModelを示す
      }, :order => "relation_order desc" )
    return max_rec.relation_order
  end

  # Javaだとabstractの代わりに例外を投げるメソッドを書くのは普通だと思っているの
 # だけど、RonR的にはどうなんだろうね…。
  # 「コード読めばわかるだろ」的な思想は、コードを読まない/書かない/仕事しないを
  # モットーにしているサラリーマンプログラマがいる世界では通用しないので、却下。
  #
  # ユニットテストコードのほうで「set_relation_class()が存在すること」を強制すれば良い
  # 気もしてきた…。
  def set_relation_class
    raise "Please set relation_class!"
  end

  def before_save
    set_relation_class
    @relation_order = max_relation_order + 1
  end
end

class ProductRelations < Relations
  belongs_to :relation_source, :class_name => 'Product', :foreign_key => 'source_id'
  belongs_to :related, :class_name => 'Product', :foreign_key => 'related_id'
  # …conditionsでrelated_classを指定しないとダメかなぁ…。

  def set_relation_class
    @relation_class = 'Product'
  end
end

こんな感じだったかなぁ…。で、今ユニットテストで検証中、save()周りがちと怪しかった気がした…。

ProductRelationは、ほかにProjectRelationとかArchiveRelationとか色々…。リンク先レコードの実在証明はbelongs_toがしてくれたと思うし、その他の検査要素は値の範囲とかになるはずなので、validate()もベースクラスで共通化できるかも。

本当はRelationのユニークキーを複合キーにしたかったのだけど、複合キーのサポートはベータ版の機能を導入する必要があるのと、それが使っているRailsで動くのか不安があるのと、そもそもrelation_order以外全部キーにしないとレコードを一意にできない、という可能性に気付いて止めた。

#2010.01.19 更新

save_before/after→before/after_saveの間違い。ちなみにsave_before/afterはFSFikiあたりと誤認した可能性がちらほら。

|

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/28934/47304749

この記事へのトラックバック一覧です: Ruby on Railsでデータの抽象化をしてみる。:

» save_beforeはFswiki だ>あたし トラックバック 哀と欲望の日々。
正しくはbefore_save。 仕事で使ってるRails 1.2.6にもちゃんとある。 ちゃんぽん [続きを読む]

受信: 2010/01/19 22:04

コメント

コメントを書く