named_scope more detail

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

Upload: kyosuke-morohashi

Post on 19-Jan-2015

5.189 views

Category:

Technology


4 download

DESCRIPTION

detail explanation of named_scope, Rails 2.1's killer future. lang:ja

TRANSCRIPT

Page 1: named_scope more detail

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

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

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

Page 2: named_scope more detail

What’s this?• with_scopeで使っていた「スコープを掛けた」検索を、クラスメソッドとして定義できるようになる仕組みです。

• ちょうどRails 2.0からwith_scope()がprotected(笑)になって困っていた人にとっての福音となりそうです。

• 定義したスコープは、都度実行されるわけでなく、 AssociationProxy的にレイジーに賢く実行されます。

• Rails 2.1の目玉機能でRubyKaigiで松田さんが話しましたhttp://www.slideshare.net/a_matsuda/new-wave-of-database-programming-with-ruby-19-on-rails-21/

Page 3: named_scope more detail

with_scopeってなんだっけ

Recipe 077 (p.220)

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

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

Page 4: named_scope more detail

なにができるの?• named_scope()で定義した「集合」を掛け合わせて操作できます。

• RDBMSをOOPの世界に引っ張ってきたORMは偉大ではありますが、その過程で集合演算という色合いは薄れ、単なるSQL/RDBMSはオブジェクトの永続化方法のように扱われてしまいがちです

• ORMを使うとSQL知らなくても。。。という言質が端的に示していますね

• 集合演算を再び我が手に!! ということでnamed_scope()の説明です。

Page 5: named_scope more detail

Examples

Page 6: named_scope more detail

active usersclass 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 7: named_scope more detail

active users(with guideline)

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 8: named_scope more detail

hot n usersuse scope with args

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

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

Page 9: named_scope more detail

describe “人気トップ3のユーザ” do before do @users = User.hot(3) end

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

hot n usersuse scope with args

Page 10: named_scope more detail

hot active userscrossover 2 scopes

describe "BAN! dahlia" do before(:each) do users(:dahlia).update_attribute(:deleted, true) end

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

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

Page 11: named_scope more detail

update using scopeclass User < ActiveRecord::Base named_scope :active, :conditions=>["#{table_name}.deleted = ?", false] do def ban self.update_all(:deleted => true) end end

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

Page 12: named_scope more detail

update using scope

describe “User.active.ban” do it "activeユーザ全員がBANされること" do User.active.ban User.should have(0).active end

it "UPDATE文は一度だけ発行されること" do User.connection.should_receive(:update).once User.active.ban endend

Page 13: named_scope more detail

Implementations

Page 14: named_scope more detail

ActiveRecord::Baseへ追加されるメソッド

•ActiveRecord::Base.scopes()

• 定義されているscopeを返す。 inheritable_attributeで保持されてる。

•ActiveRecord::Base.named_scope()

• 第一引数にscope名,第二引数にHash形式でfindパラメータを渡す

• 第二引数にfindパラメータを返すProcを渡すと実行時に評価される。

Page 15: named_scope more detail

named_scope宣言をkwsk

• named_scope宣言でscopeを定義する

• 内部では実行時にScopeオブジェクトを作るProcを作る。

• それを呼び出すクラスメソッドも追加される。• ブロック付きで呼び出すと無名モジュールを作ってScopeオブジェクトに追加する

• このブロックは第二引数のProcオブジェクトとは別物。このへんが1.9だとキレイだという所以。

Page 16: named_scope more detail

AR::Base.named_scopedef named_scope(name, options = {}, &block) scopes[name] = lambda do |parent_scope, *args| Scope.new(parent_scope, case options when Hash options when Proc options.call(*args) end, &block) end (class << self; self end).instance_eval do define_method name do |*args| scopes[name].call(self, *args) end endend

Page 17: named_scope more detail

Scopeオブジェクト

•ArrayのようでArrayじゃないアレ

• みんな大好きAssociationProxyと同じ感じ• Arrayぽい動きはload_found()でロードしたモデルオブジェクトのArrayに委譲

• みんな大好きmethod_missingで頑張ってる

Page 18: named_scope more detail

Scope.new(parent,options,&block)

• 第一引数は親スコープ• Scopeインスタンス or AR::Base• 最初は必ずAR::Base

• 第二引数がfindパラメータ• ブロックを渡すとScopeオブジェクトのメソッドを追加できる

‣ ex => User.active.ban

Page 19: named_scope more detail

Scope#method_missing

• method_missingで「親スコープ」を使う• 呼ばれたメソッドに対応する名前付きscopeがあれば子Scopeを作るProcをcallする

• 子Procには親スコープとしてselfを渡す• なければ自身のfindパラメータを使って親スコープの当該メソッドを呼び出す

Page 20: named_scope more detail

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 21: named_scope more detail

Conclusion

Page 22: named_scope more detail

call sequence of

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

Page 23: named_scope more detail

call sequence- generate Scope instance -

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

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

# scope[:hot] != nilであるため#=> <#Scope:hot, @proxy_scope=> <# Scope:active>>

Page 24: named_scope more detail

call sequence- 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 25: named_scope more detail

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

Page 26: named_scope more detail

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

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

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

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

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

Page 27: named_scope more detail

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 28: named_scope more detail

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

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

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

旧これが

こうなる

Page 29: named_scope more detail

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