● [Rails] 関連名による INNER JOIN が欲しい
从 ’w’)<舞波ね、悩みがあるの
|
|
has_many
→
|
|
has_many
→
|
| favorites |
| id |
| member_id |
| name |
|
|
从*’w’)<OUTER JOIN はすっごいエレガントなの♪
Member.find(:all, :include=>[:group, :favorites]) |
从 ;゜w゜)<でも、INNER JOIN だと生SQLに逆戻り!!
Member.find(:all, :joins=>"INNER JOIN groups ON groups.id = members.group_id INNER JOIN favorites ...") |
_, ,_
从 ’w’) ∩ < ヤダヤダ、もう耐えられないのー!シンボル名でJOINしたいの〜!
⊂ (
ヽ∩ つ ジタバタ
〃〃
● symbolic join
vendor/plugins/symbolic_join/init.rb
class ActiveRecord::Base
class << self
private
def add_joins!(sql, options, scope = :auto)
scope = scope(:find) if :auto == scope
join = (scope && scope[:joins]) || options[:joins]
sql << " #{expand_join_query(join)} " if join
end
def expand_join_query(*joins)
joins.flatten.map{|join|
case join
when Symbol
ref = reflections[join] or
raise ActiveRecord::ActiveRecordError, "Could not find the source association :#{join} in model #{self}"
case ref.macro
when :belongs_to
"INNER JOIN %s ON %s.%s = %s.%s" % [ref.table_name, ref.table_name, primary_key, table_name, ref.primary_key_name]
else
"INNER JOIN %s ON %s.%s = %s.%s" % [ref.table_name, ref.table_name, ref.primary_key_name, table_name, primary_key]
end
else
join.to_s
end
}.join(" ")
end
end
end |
从*’w’)<とりあえずこれで以下は動くようになる
Member.find(:all, :joins=>[:group, :favorites]) |
○☆ノ_,,_ ○
((从# ’w’) ))<でも、Cascade マニアとしては多段できないと嫌なの!意地でも対応させるの!
○ ( ) ○
从 ;゜w゜)<多段対応したら、ちっちゃくなっちゃった!
class ActiveRecord::Base
class << self
private
def add_joins!(sql, options, scope = :auto)
scope = scope(:find) if :auto == scope
join = (scope && scope[:joins]) || options[:joins]
sql << " #{expand_join_query(join)} " if join
end
def expand_join_query(*joins)
strings, joins = joins.flatten.partition{|i| i.class == String}
string_join = strings.join(' ')
left_joins = JoinDependency.new(self, joins, string_join).join_associations.map(&:association_join)
left_joins.join(" ").gsub(/LEFT OUTER/, "INNER") + string_join
end
end
end |
JoinDependency クラスを無駄に作ってた舞波は、反YAGNI界の神。
从*’w’)<クゥ〜ン♪
Member.find(:all, :joins=>[:group, :favorites])
=> SELECT * FROM members
INNER JOIN groups ON groups.id = members.group_id
INNER JOIN favorites ON favorites.member_id = members.id
Member.find(:all, :joins=>[:group, "INNER JOIN prefs USING(prefcode)"])
=> SELECT * FROM members
INNER JOIN groups ON groups.id = members.group_id
INNER JOIN prefs USING(prefcode)
Group.find(:all, :joins=>[{:members=>:favorites}, :songs])
=> SELECT * FROM groups
INNER JOIN members ON members.group_id = groups.id
INNER JOIN favorites ON favorites.member_id = members.id
INNER JOIN songs ON songs.group_id = groups.id |
一応プラグイン (上記のままだけれどディレクトリを掘るのも面倒な人向け)
ruby script/plugin install http://wota.jp/svn/rails/plugins/branches/stable/symbolic_join
特徴
- 関連名をシンボル名で指定したINNER JOIN
- 従来の文字列(生SQL)との併用
- Cascaded 対応
制限
- INNER JOIN 強制 (LEFT は :include を使う方向で)
- 1:多をJOINすると結果セット数が増える (一覧表示用で)
- :include と併用は不明 (何が起こるのか不明)
本気で考えていないのでどこまで使えるかは不明です。(特に Association 系で)。あと、LEFT OUTER を gsub で置換してる手抜き処理なのでいつまで使えるかも無保証です。
● 今後
正しくは JoinAssociation#association_join(join_type = "LEFT OUTER") にすベッキー。で、JOIN 節の生成は各 Reflection に dispatch した方がいい気がする。Reflection は自分の「関係」をもとに結合構造を作り、具体的な表記に関してはさらに ConnectionAdapters に dispatch して、各DBのシンタックスの差異を吸収する・・・とかどーたらこーたら、3点シュートみたいな恋がしたい現実。じゃないともう association_join は手がつけられましぇん。うまく行けば RubyKaigi のネタになるかな?