世界線航跡蔵

Mad web programmerのYuguiが技術ネタや日々のあれこれをお送りします。

2006年08月04日

has_one :through

Rails 1.1以降のActiveRecordではhas_and_belongs_to_manyの代わりにhas_many :throughを使うのがトレンド。これはこれで便利なのだけれど、Activity Based Datamodel式のテーブル構造を使うためにはやっぱりhas_one :throughも欲しい。

本家のMLでは7月24日の"belongs_to :through?"とかいうスレッドでdelegate使えとか言われてる。でも、その場合発行されるSQLが効率悪そうで嫌だ。やっぱり結合して一気に持って来たい。

で、作ってみた。プラグインでも、environments.rbに直接書き込みでも好きなようにインストールしてください。暫定版だし。

  • ActiveRecord本体の関連の実装に激しく依存してるというたちの悪さ
  • ActiveRecord 1.14.3で動作確認。他は知らない。
  • やっぱりこれはプラグインよりはパッチとして書くほうがいいのかも。
  • でもrails-coreの人たちはhas_one :through入れる気無いんでない? ソース見ると敢えて実装しなかったとしか思えないんだけど。
  • その辺どうなの? 助けてー、マイハマン。
  • polymorphic associationには未対応というか、その辺よくわからないのでとばした。
  • has_many :throughと同じで、:throughオプションつきの関連を直接createやbuildはできない。つまり「リソース系」を直接追加はできない。「イベント系」を作るときに属性に加えること。そうしないと「イベント系」をどうやって作ったものか迷ってしまうものね。
  • '''バグがあったので直しました''' 2006-08-05 09:30:00 JST

使いかた

早い話が、こう書きたいわけね。

class Member < ActiveRecord::Base
  has_one :membership
  has_one :group, :through => :membership
end
class Group < ActiveRecord::Base
  has_many :memberships
  has_many :members, :through => :memberships
end
class Membership < ActiveRecord::Base
  belongs_to :member
  belongs_to :group
end

...

alice = Member.find(1)
alice.group             # instead of alice.membership.group :-)

代物

class ActiveRecord::Associations::HasOneThroughAssociation < ActiveRecord::Associations::HasOneAssociation
  private
    def construct_sql
      @finder_sql = "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.primary_key_name} = #{@owner.quoted_id}"
      @finder_sql << " AND (#{conditions})" if conditions
    end

    def find_target
      @reflection.klass.find(:first,
        :select => @reflection.options[:select] || "#{@reflection.table_name}.*",
        :conditions => @finder_sql,
        :order => @reflection.options[:order],
        :include => @reflection.options[:include],
        :joins => "LEFT OUTER JOIN #{@reflection.through_reflection.table_name} " +
                  " ON #{@reflection.through_reflection.table_name}.#{@reflection.source_reflection.primary_key_name} " +
                "    = #{@reflection.table_name}.#{@reflection.klass.primary_key}"
      )
    end
end

module ActiveRecord::Associations::ClassMethods
  def has_one_with_through(association_id, options = {})
    if options[:through]
      reflection = create_has_one_through_reflection(association_id, options)
      association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
    else
      has_one_without_through(association_id, options)
    end
  end

  alias_method :has_one_without_through, :has_one
  alias_method :has_one, :has_one_with_through

  private
    def create_has_one_through_reflection(association_id, options)
      options.assert_valid_keys(
        :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through
      )
      create_reflection(:has_one, association_id, options, self)
    end
end

トラックバック

http://yugui.jp/articles/496/ping
Rails勉強会@東京 第9回 (ratio - rational - irrational)
さて、Rails勉強会@東京 第9回に行ってきた。昨日はLL Ringにも行っ...

コメント

もろはし (2006年08月05日 09時39分14秒)
<p>ktkr<br />超GJ!!<br /></p>
Yugui (2006年08月05日 12時17分06秒)
<p>ありがとうございます。<br />欲しいっていってる人が結構いたので作っちゃいました。</p>
舞波 (2006年08月05日 23時13分19秒)
<p>最近この辺を丁度悩んでたのでナイスタイミングです!<br />FKは誰が持つべき?テーブルの粒度は?ABDは究極の解?etc...<br />ただ無かっただけで、coreが嫌ってるということはないと思います。</p> <p>是非ともプラグイン化きぼん!(コピペで済むモノでも敷居が低くなる説)<br />スバラシス!Yuguiたん、カワユス!<br /></p>
Yugui (2006年08月06日 20時58分44秒)
<p>&gt; coreが嫌ってるということはないと思います。<br />舞波さんキタ━━━━━━━━(゜∀゜)━━━━━━━━━!!</p> <p>そうですか。私の被害妄想ですか。ありがとうございます。</p> <p>今度英語をタイプする気力があるときにIRCで「入れる気ない?」って聞いてみようと思います。</p>
blog comments powered by Disqus

ご案内

前の記事
次の記事

タグ一覧

過去ログ

  1. 2016年07月
  2. 2016年01月
  3. 2015年09月
  4. 2015年08月
  5. 過去ログ一覧

フィード

フィードとは

その他

Powered by "rhianolethe" the blog system