To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / .svn / pristine / 83 / 83c3e4ac7042c55f0008c2a13f02fb3cd6cd5e71.svn-base @ 1297:0a574315af3e
History | View | Annotate | Download (5.67 KB)
| 1 |
# Taken from Rails 2.1 |
|---|---|
| 2 |
module CollectiveIdea #:nodoc: |
| 3 |
module NamedScope #:nodoc: |
| 4 |
# All subclasses of ActiveRecord::Base have two named_scopes: |
| 5 |
# * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and |
| 6 |
# * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: |
| 7 |
# |
| 8 |
# Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
|
| 9 |
# |
| 10 |
# These anonymous scopes tend to be useful when procedurally generating complex queries, where passing |
| 11 |
# intermediate values (scopes) around as first-class objects is convenient. |
| 12 |
def self.included(base) |
| 13 |
base.class_eval do |
| 14 |
extend ClassMethods |
| 15 |
named_scope :scoped, lambda { |scope| scope }
|
| 16 |
end |
| 17 |
end |
| 18 |
|
| 19 |
module ClassMethods #:nodoc: |
| 20 |
def scopes |
| 21 |
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
| 22 |
end |
| 23 |
|
| 24 |
# Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query, |
| 25 |
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
|
| 26 |
# |
| 27 |
# class Shirt < ActiveRecord::Base |
| 28 |
# named_scope :red, :conditions => {:color => 'red'}
|
| 29 |
# named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] |
| 30 |
# end |
| 31 |
# |
| 32 |
# The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, |
| 33 |
# in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
|
| 34 |
# |
| 35 |
# Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object |
| 36 |
# constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>, |
| 37 |
# <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
|
| 38 |
# as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>, |
| 39 |
# <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array. |
| 40 |
# |
| 41 |
# These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only. |
| 42 |
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments |
| 43 |
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>. |
| 44 |
# |
| 45 |
# All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to |
| 46 |
# <tt>has_many</tt> associations. If, |
| 47 |
# |
| 48 |
# class Person < ActiveRecord::Base |
| 49 |
# has_many :shirts |
| 50 |
# end |
| 51 |
# |
| 52 |
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean |
| 53 |
# only shirts. |
| 54 |
# |
| 55 |
# Named scopes can also be procedural. |
| 56 |
# |
| 57 |
# class Shirt < ActiveRecord::Base |
| 58 |
# named_scope :colored, lambda { |color|
|
| 59 |
# { :conditions => { :color => color } }
|
| 60 |
# } |
| 61 |
# end |
| 62 |
# |
| 63 |
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
| 64 |
# |
| 65 |
# Named scopes can also have extensions, just as with <tt>has_many</tt> declarations: |
| 66 |
# |
| 67 |
# class Shirt < ActiveRecord::Base |
| 68 |
# named_scope :red, :conditions => {:color => 'red'} do
|
| 69 |
# def dom_id |
| 70 |
# 'red_shirts' |
| 71 |
# end |
| 72 |
# end |
| 73 |
# end |
| 74 |
# |
| 75 |
# |
| 76 |
# For testing complex named scopes, you can examine the scoping options using the |
| 77 |
# <tt>proxy_options</tt> method on the proxy itself. |
| 78 |
# |
| 79 |
# class Shirt < ActiveRecord::Base |
| 80 |
# named_scope :colored, lambda { |color|
|
| 81 |
# { :conditions => { :color => color } }
|
| 82 |
# } |
| 83 |
# end |
| 84 |
# |
| 85 |
# expected_options = { :conditions => { :colored => 'red' } }
|
| 86 |
# assert_equal expected_options, Shirt.colored('red').proxy_options
|
| 87 |
def named_scope(name, options = {}, &block)
|
| 88 |
scopes[name] = lambda do |parent_scope, *args| |
| 89 |
Scope.new(parent_scope, case options |
| 90 |
when Hash |
| 91 |
options |
| 92 |
when Proc |
| 93 |
options.call(*args) |
| 94 |
end, &block) |
| 95 |
end |
| 96 |
(class << self; self end).instance_eval do |
| 97 |
define_method name do |*args| |
| 98 |
scopes[name].call(self, *args) |
| 99 |
end |
| 100 |
end |
| 101 |
end |
| 102 |
end |
| 103 |
|
| 104 |
class Scope #:nodoc: |
| 105 |
attr_reader :proxy_scope, :proxy_options |
| 106 |
[].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
|
| 107 |
delegate :scopes, :with_scope, :to => :proxy_scope |
| 108 |
|
| 109 |
def initialize(proxy_scope, options, &block) |
| 110 |
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
|
| 111 |
extend Module.new(&block) if block_given? |
| 112 |
@proxy_scope, @proxy_options = proxy_scope, options.except(:extend) |
| 113 |
end |
| 114 |
|
| 115 |
def reload |
| 116 |
load_found; self |
| 117 |
end |
| 118 |
|
| 119 |
protected |
| 120 |
def proxy_found |
| 121 |
@found || load_found |
| 122 |
end |
| 123 |
|
| 124 |
private |
| 125 |
def method_missing(method, *args, &block) |
| 126 |
if scopes.include?(method) |
| 127 |
scopes[method].call(self, *args) |
| 128 |
else |
| 129 |
with_scope :find => proxy_options do |
| 130 |
proxy_scope.send(method, *args, &block) |
| 131 |
end |
| 132 |
end |
| 133 |
end |
| 134 |
|
| 135 |
def load_found |
| 136 |
@found = find(:all) |
| 137 |
end |
| 138 |
end |
| 139 |
end |
| 140 |
end |