named.rb 7.9 KB
Newer Older
1 2
# frozen_string_literal: true

3 4 5
require "active_support/core_ext/array"
require "active_support/core_ext/hash/except"
require "active_support/core_ext/kernel/singleton_class"
6 7

module ActiveRecord
8
  # = Active Record \Named \Scopes
9 10 11 12 13
  module Scoping
    module Named
      extend ActiveSupport::Concern

      module ClassMethods
14
        # Returns an ActiveRecord::Relation scope object.
15
        #
16
        #   posts = Post.all
17 18 19
        #   posts.size # Fires "select count(*) from  posts" and returns the count
        #   posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
        #
20
        #   fruits = Fruit.all
21
        #   fruits = fruits.where(color: 'red') if options[:red_only]
22 23
        #   fruits = fruits.limit(10) if limited?
        #
24
        # You can define a scope that applies to all finders using
25
        # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope].
26
        def all
27 28
          current_scope = self.current_scope

J
Jon Leighton 已提交
29
          if current_scope
30 31 32 33 34
            if self == current_scope.klass
              current_scope.clone
            else
              relation.merge!(current_scope)
            end
35
          else
36
            default_scoped
37
          end
38
        end
J
Jon Leighton 已提交
39

40 41 42 43 44 45 46 47 48 49
        def scope_for_association(scope = relation) # :nodoc:
          current_scope = self.current_scope

          if current_scope && current_scope.empty_scope?
            scope
          else
            default_scoped(scope)
          end
        end

50
        def default_scoped(scope = relation) # :nodoc:
51
          build_default_scope(scope) || scope
52
        end
53

54 55 56
        def default_extensions # :nodoc:
          if scope = current_scope || build_default_scope
            scope.extensions
57
          else
58
            []
59
          end
60 61
        end

62
        # Adds a class method for retrieving and querying objects.
63
        # The method is intended to return an ActiveRecord::Relation
64
        # object, which is composable with other scopes.
65
        # If it returns +nil+ or +false+, an
66
        # {all}[rdoc-ref:Scoping::Named::ClassMethods#all] scope is returned instead.
67 68
        #
        # A \scope represents a narrowing of a database query, such as
69
        # <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
70 71
        #
        #   class Shirt < ActiveRecord::Base
72 73
        #     scope :red, -> { where(color: 'red') }
        #     scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
74 75
        #   end
        #
76
        # The above calls to #scope define class methods <tt>Shirt.red</tt> and
77 78
        # <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
        # represents the query <tt>Shirt.where(color: 'red')</tt>.
79
        #
80
        # You should always pass a callable object to the scopes defined
81
        # with #scope. This ensures that the scope is re-evaluated each
82 83 84 85
        # time it is called.
        #
        # Note that this is simply 'syntactic sugar' for defining an actual
        # class method:
86 87 88
        #
        #   class Shirt < ActiveRecord::Base
        #     def self.red
89
        #       where(color: 'red')
90 91 92
        #     end
        #   end
        #
93
        # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
94
        # <tt>Shirt.red</tt> is not an Array but an ActiveRecord::Relation,
95
        # which is composable with other scopes; it resembles the association object
96 97
        # constructed by a {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
        # declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
98 99 100 101
        # <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
        # association objects, named \scopes act like an Array, implementing
        # Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
        # and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
102
        # <tt>Shirt.red</tt> really was an array.
103 104 105 106 107 108 109 110 111 112
        #
        # 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. Nested finds and calculations also work
        # with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
        # returns the number of garments for which these criteria obtain.
        # Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
        #
        # All scopes are available as class methods on the ActiveRecord::Base
        # descendant upon which the \scopes were defined. But they are also
113 114
        # available to {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
        # associations. If,
115 116 117 118 119
        #
        #   class Person < ActiveRecord::Base
        #     has_many :shirts
        #   end
        #
120 121
        # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
        # Elton's red, dry clean only shirts.
122
        #
123 124
        # \Named scopes can also have extensions, just as with
        # {has_many}[rdoc-ref:Associations::ClassMethods#has_many] declarations:
125 126
        #
        #   class Shirt < ActiveRecord::Base
127
        #     scope :red, -> { where(color: 'red') } do
128 129 130 131 132 133 134 135 136
        #       def dom_id
        #         'red_shirts'
        #       end
        #     end
        #   end
        #
        # Scopes can also be used while creating/building a record.
        #
        #   class Article < ActiveRecord::Base
137
        #     scope :published, -> { where(published: true) }
138 139 140 141 142
        #   end
        #
        #   Article.published.new.published    # => true
        #   Article.published.create.published # => true
        #
143
        # \Class methods on your model are automatically available
144 145 146
        # on scopes. Assuming the following setup:
        #
        #   class Article < ActiveRecord::Base
147
        #     scope :published, -> { where(published: true) }
148
        #     scope :featured, -> { where(featured: true) }
149 150 151 152 153 154
        #
        #     def self.latest_article
        #       order('published_at desc').first
        #     end
        #
        #     def self.titles
155
        #       pluck(:title)
156 157 158 159 160 161 162
        #     end
        #   end
        #
        # We are able to call the methods like this:
        #
        #   Article.published.featured.latest_article
        #   Article.featured.titles
J
Jon Leighton 已提交
163
        def scope(name, body, &block)
A
Andrey Deryabin 已提交
164
          unless body.respond_to?(:call)
165
            raise ArgumentError, "The scope body needs to be callable."
166
          end
167

168 169 170 171 172 173
          if dangerous_class_method?(name)
            raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
              "on the model \"#{self.name}\", but Active Record already defined " \
              "a class method with the same name."
          end

174 175 176 177 178 179
          if method_defined_within?(name, Relation)
            raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
              "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \
              "an instance method with the same name."
          end

180
          valid_scope_name?(name)
J
Jon Leighton 已提交
181
          extension = Module.new(&block) if block
182

183 184
          if body.respond_to?(:to_proc)
            singleton_class.send(:define_method, name) do |*args|
185
              scope = all
186
              scope = scope._exec_scope(*args, &body)
187
              scope = scope.extending(extension) if extension
188
              scope
189 190 191
            end
          else
            singleton_class.send(:define_method, name) do |*args|
192 193
              scope = all
              scope = scope.scoping { body.call(*args) || scope }
194
              scope = scope.extending(extension) if extension
195
              scope
196
            end
197 198
          end
        end
199

200
        private
201

202 203 204 205 206
          def valid_scope_name?(name)
            if respond_to?(name, true) && logger
              logger.warn "Creating scope :#{name}. " \
                "Overwriting existing method #{self.name}.#{name}."
            end
207
          end
208 209 210 211
      end
    end
  end
end