named_scope more detail - WebCareer

32
named_scopeついてくわしく (株)永和システムマネジメント || Rails勉強会@東京 諸橋 恭介(もろはし きょうすけ) [email protected] (work) [email protected] (private)

description

talked about AR.named_scope in Rails Summer Festival 2008 by WebCareer

Transcript of named_scope more detail - WebCareer

Page 1: named_scope more detail - WebCareer

named_scopeについてくわしく(株)永和システムマネジメント || Rails勉強会@東京

諸橋 恭介(もろはし きょうすけ)

[email protected] (work)[email protected] (private)

Page 2: named_scope more detail - WebCareer

まとめ

•named_scopeを使うと、複雑な検索条件をシンプルかつ柔軟に記述できる

•技術的にずいぶんおもしろい•とてもかっこいいmethod_missingのよい使い方

Page 3: named_scope more detail - WebCareer

これはなに?

•かつてRailsには with_scope というAPIがありました

•with_scopeを使うと、検索条件を指定する「スコープ」を定義できます

•そのスコープ内での検索には、自動的にスコープの条件が付加されます

Page 4: named_scope more detail - WebCareer

with_scopeの例User.find(:all)# SELECT * FROM users

User.find(:all, :conditions=>[“deleted=?”,false])# SELECT * FROM users WHERE deleted = 0

User.with_scope( :find=>{:conditions=>[“deleted=?”,false]}) do User.find(:all)end# SELECT * FROM users WHERE deleted = 0

Page 5: named_scope more detail - WebCareer

with_scopeについて

Recipe 077 (p.220)

検索条件をスコープ毎にまとめて定義する

http://www.amazon.co.jp/dp/4797336625

Page 6: named_scope more detail - WebCareer

What’s named_scope?

•with_scopeで使っていた「検索スコープ」に名前が付けられます。

•Rails 2.1の目玉機能です• RubyKaigi 2008でも松田さんが話しました

http://www.slideshare.net/a_matsuda/new-wave-of-database-programming-with-ruby-19-on-rails-21/

Page 7: named_scope more detail - WebCareer

なにがすごいの?•名前を付けたスコープ同士を掛け合わせられます。• DBアクセスは、 必要となった時点で賢く(多くは1回だけ)実行されます。

•「微妙に条件の異なる検索」の多くをスコープの掛け合わせで表現できます

Page 8: named_scope more detail - WebCareer

ORMとRDBMS•RDBMSをOOPの世界に引っ張ってきたORMはすごい!!

•しかしORMを使うと、RDBMSは単なるオブジェクトの永続化ストレージのように扱いがちです。

•named_scopeを使うと、RDBの集合演算が持つ力の一部をOOPの世界でも使える感じになります。

Page 9: named_scope more detail - WebCareer

named_scopeの例

Page 10: named_scope more detail - WebCareer

有効なユーザusers

active users

Page 11: named_scope more detail - WebCareer

class User < ActiveRecord::Base named_scope(:active, {:condition=>["deleted = ?", false]})end

describe “activeなユーザ” do it “は全員削除されて*いない*こと” do User.active.should be_all{|u| not u.deleted } endend

有効なユーザ

Page 12: named_scope more detail - WebCareer

users

hot users

人気のあるユーザ実行時にscopeの一部を指定

Page 13: named_scope more detail - WebCareer

class User < ActiveRecord::Base named_scope :active, :condition=>["deleted = ?", false]

named_scope :hot, Proc.new{|arg| {:conditions=>[“popularity > ?”, arg], :order =>"popularity DESC"} }end

人気のあるユーザ実行時にscopeの一部を指定

Page 14: named_scope more detail - WebCareer

describe “人気度80以上のユーザ” do before do @users = User.hot(80) end

it "のうちトップはdahliaであること" do @users.first.should == users(:dahlia) endend

人気のあるユーザ実行時にscopeの一部を指定

Page 15: named_scope more detail - WebCareer

users

active usershot users

人気かつ有効なユーザcrossover 2 scopes

Page 16: named_scope more detail - WebCareer

describe "BAN! dahlia" do before(:each) do # dahliaの削除フラグを立てる つまり active usersの集合から外す users(:dahlia).update_attribute(:deleted, true) end

it "アクティブユーザで最も人気なのはcharlesであること" do User.active.hot(80).first.should == users(:charles) end

it "クエリ発行(select_all)は一回だけ呼ばれること" do User.connection.should_receive(:select_all).once.and_return([]) User.hot(80).active.find(:all) endend

人気かつ有効なユーザcrossover 2 scopes

Page 17: named_scope more detail - WebCareer

users

active usershot n users

他にも好きな条件を追加できる

Other..

Page 18: named_scope more detail - WebCareer

named_scope実行時の動き

User.active.hot(80).each do |u| do_somethingend

Page 19: named_scope more detail - WebCareer

User.active.hot(80)- generate Scope instance -

User#active #=> <#Scope:active, @proxy_scope=>User>

<#Scope:active>#hot(3)---> <#Scope:active>#method_missing(:hot)

# scopes.include?(:hot)であるため#=> <#Scope:hot, @proxy_scope=> <# Scope:active>>

Page 20: named_scope more detail - WebCareer

Scope#method_missing

•User#activeはUserクラスを親スコープとしたScopeを作る

•Scope#method_missingは呼ばれたメソッドに対応する子Scopeを作る• 子Procには親スコープとしてselfを渡す

Page 21: named_scope more detail - WebCareer

Scope#method_missing

def method_missing(method, *args, &block) if scopes.include?(method) scopes[method].call(self, *args) else with_scope :find => proxy_options do proxy_scope.send(method, *args, &block) end endend

Page 22: named_scope more detail - WebCareer

User.active.hot(80)- execute and respond to method -

<#Scope:hot, @proxy_scope=> <#Scope:active>>#each---> <#Scope:hot>#proxy_found---> <#Scope:hot>#find(:all)---> <#Scope:hot>method_missing(:find)---> <#Scope:hot>#with_scope{ <#Scope:active>#find }---> <#Scope:active>#method_missing(:find)---> <#Scope:active>#with_scope{ User.find(:all) }# => [<#User:..>, .... ]

[<#User:..>, ...].each{ do_something }

Page 23: named_scope more detail - WebCareer

Scope#method_missing

•呼ばれたメソッドに対応するScopeが定義されていない場合

• 自身のfindパラメータでwith_scopeし、親スコープの当該メソッドを呼び出す

Page 24: named_scope more detail - WebCareer

Scope#method_missing

def method_missing(method, *args, &block) if scopes.include?(method) scopes[method].call(self, *args) else with_scope :find => proxy_options do proxy_scope.send(method, *args, &block) end endend

Page 25: named_scope more detail - WebCareer

User.with_scope( :find=>{:conditions=>[”popularity>?”, 80], :order=>”popularity” }) do

User.with_scope( :find=>{:conditons=>[“deleted=?”, false]}) do

User.find(:all).each{ ... }

endend

with_scopeで書き直したイメージ

Rails2.xではこのままでは動きません

Page 26: named_scope more detail - WebCareer

まとめ

•named_scopeを使うと、複雑な検索条件をシンプルかつ柔軟に記述できる

•技術的にずいぶんおもしろい•とてもかっこいいmethod_missingのよい使い方

Page 27: named_scope more detail - WebCareer

you.any?{|u| u.question?}

Page 28: named_scope more detail - WebCareer

FAQ: スコープはどんなふうに結合されるの?

•後ろから順にwith_scopeを使います。

• WHERE句相当(:conditionsパラメータ)はANDで結合されます。

• ORDERやLIMITは先に指定したscopeで指定しているものがそのまま使われるっぽいです。

• この辺りのロジックは activerecord-2.1.0/lib/active_record/base.rb : 1807 あたりを参照

Page 29: named_scope more detail - WebCareer

FAQ: Ruby 1.9だとよりカッコ良いという噂があるが?• Procオブジェクト生成に->記法が使えます

named_scope :hot, Proc.new{|arg| {:order =>"popularity DESC", :limit => arg} }

named_scope :hot, ->{ {:order =>"popularity DESC", :limit => arg} }

旧これが

こうなる

Page 30: named_scope more detail - WebCareer

FAQ: テストがよりカッコ良く書けるという噂があるが?• mockするのが楽にキレイになりそうな感じ。

User.should_receive(:find). with(:all,:condition=>[...]). and_return( [alice] )

User.shold_receive(:active) and_return( [alice] )

旧これが

こうなる

Page 31: named_scope more detail - WebCareer

with_scopeについて

Recipe 077 (p.220)

検索条件をスコープ毎にまとめて定義する

http://www.amazon.co.jp/dp/4797336625

Page 32: named_scope more detail - WebCareer

ご清聴ありがとうございました