狠狠撸

狠狠撸Share a Scribd company logo
named_scopeに
ついてくわしく
(株)永和システムマネジメント ?? Rails勉強会@東京
諸橋 恭介(もろはし きょうすけ)
k-morohashi@esm.co.jp (work)
moronatural@gmail.com (private)
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/
with_scopeってなんだっけ
Recipe 077 (p.220)
検索条件をスコープ毎に
まとめて定義する
http://www.amazon.co.jp/dp/4797336625
なにができるの?
? named_scope()で定義した「集合」を掛け合わせて
操作できます。
? RDBMSをOOPの世界に引っ張ってきたORMは偉大
ではありますが、その過程で集合演算という色合いは
薄れ、単なるSQL/RDBMSはオブジェクトの永続化
方法のように扱われてしまいがちです
? ORMを使うとSQL知らなくても。。。という言質が端的に示してい
ますね
? 集合演算を再び我が手に!! ということで
named_scope()の説明です。
Examples
active users
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 }
end
end
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 }
end
end
hot n users
use 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
describe “人気トップ3のユーザ” do
before do
@users = User.hot(3)
end
it "のうちトップはdahliaであること" do
@users.first.should == users(:dahlia)
end
end
hot n users
use scope with args
hot active users
crossover 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)
end
end
update using scope
class 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
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
end
end
Implementations
ActiveRecord::Baseへ
追加されるメソッド
?ActiveRecord::Base.scopes()
? 定義されているscopeを返す。?
inheritable_attributeで保持されてる。
?ActiveRecord::Base.named_scope()
? 第一引数にscope名,第二引数にHash形式で?nd
パラメータを渡す
? 第二引数に?ndパラメータを返すProcを渡すと実行
時に評価される。
named_scope宣言をkwsk
?named_scope宣言でscopeを定義する
? 内部では実行時にScopeオブジェクトを作るProc
を作る。
? それを呼び出すクラスメソッドも追加される。
? ブロック付きで呼び出すと無名モジュールを作って
Scopeオブジェクトに追加する
? このブロックは第二引数のProcオブジェクトとは
別物。このへんが1.9だとキレイだという所以。
AR::Base.named_scope
def 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
end
end
Scopeオブジェクト
?ArrayのようでArrayじゃないアレ
? みんな大好きAssociationProxyと同じ感じ
? Arrayぽい動きはload_found()でロードしたモ
デルオブジェクトのArrayに委譲
? みんな大好き尘别迟丑辞诲冲尘颈蝉蝉颈苍驳で顽张ってる
Scope.new(parent,options,&block)
? 第一引数は親スコープ
? Scopeインスタンス or AR::Base
? 最初は必ずAR::Base
? 第二引数が?ndパラメータ
? ブロックを渡すとScopeオブジェクトのメソッ
ドを追加できる
?ex => User.active.ban
Scope#method_missing
? method_missingで「親スコープ」を使う
? 呼ばれたメソッドに対応する名前付きscopeがあれば
子Scopeを作るProcをcallする
? 子Procには親スコープとしてselfを渡す
? なければ自身の?ndパラメータを使って親スコープの当該
メソッドを呼び出す
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
end
end
Conclusion
call sequence of
User.active.hot(3).each do |u|
do_something
end
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>>
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 }
you.any?{|u|
u.question?
}
FAQ: スコープはどんなふ
うに結合されるの?
?後ろから順にwith_scopeを使います。
? WHERE句相当(:conditionsパラメータ)はANDで
結合されます。
? ORDERやLIMITは先に指定したscopeで指定して
いるものがそのまま使われるっぽいです。
? この辺りのロジックは activerecord-2.1.0/lib/
active_record/base.rb : 1807 あたりを参照
FAQ: Ruby 1.9だとより
カッコ良いという噂があるが?
? Procオブジェクト生成に->記法が使えます
named_scope :hot,
Proc.new{|arg|
{:order =>"popularity DESC",
:limit => arg}
}
named_scope :hot, ->{
{:order =>"popularity DESC",
:limit => arg}
}
旧
これが
こうなる
新
FAQ: テストがよりカッコ良く
書けるという噂があるが?
? mockするのが楽にキレイになりそうな感じ。
User.should_receive(:find).
with(:all,:condition=>[...]).
and_return( [alice] )
User.shold_receive(:active)
and_return( [alice] )
旧
これが
こうなる
新
ご清聴あり
がとうござ
いました

More Related Content

named_scope more detail