To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / vendor / plugins / awesome_nested_set / lib / awesome_nested_set / named_scope.rb @ 442:753f1380d6bc
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
|