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