gist: 1792 Download_button fork
public
Public Clone URL: git://gist.github.com/1792.git
Combined Scopes - Chaining Named Scopes Easily.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# class User < ActiveRecord::Base
# named_scope :active, :conditions => { :active => true }
# named_scope :with_status, lambda {|*statuses| {:conditions => ["users.status IN (?)", statuses]}}
# named_scope :joshes, :conditions => ["first_name LIKE ?", "%Josh%"]
#
# combined_scope :active_pending, lambda { active }, lambda { with_status("pending") }
# combined_scope :active_unimportant, lambda { active }, lambda { with_status("not_activated", "cancelled", "deleted") }
# # since they are real scopes, you can combine them again; in this case, with active_pending (created with combined_scope) and joshes (created with named_scope)
# combined_scope :active_pending_joshes, lambda { active_pending }, lambda { joshes }
# end
#
# Caveat
# ======
#
# You *need* to declare all combined scopes *after* the ones they reference.
# In the example above, the named_scope :with_status needs to be declared *before*
# :active_pending and :active_unimportant. In the same manner, both scopes
# :active_pending and :joshes need to be declared before :active_pending_joshes. You
# will receive NoMethodErrors if you don't heed this warning, as the method we try to call
# doesn't exist on the model until after it's defined within named_scope
 
module ActiveRecord
  module NamedScope
    module ClassMethods
      def combined_scope(name, *procs, &block)
        options = procs.extract_options!
        
        procs.each do |proc|
          opts = proc.bind(self).call.proxy_options
          options = merge_options(options, opts)
        end
        
        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
 
      private
      
      def merge_options(options, extra_options)
        (options.keys + extra_options.keys).uniq.each do |key|
          merge = options[key] && extra_options[key]
          options[key] = \
            if key == :conditions && merge
              merge_conditions(extra_options[key], options[key])
            elsif key == :include && merge
              merge_includes(options[key], extra_options[key]).uniq
            else
              options[key] || extra_options[key]
            end
        end
        options
      end
    end
  end
end

Revisions