aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/scoping/default.rb
blob: 8baf3b80449c8dc6c8e5376c432c65e2040f01c7 (plain) (blame)
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
module ActiveRecord
  module Scoping
    module Default
      extend ActiveSupport::Concern

      included do
        # Stores the default scope for the class.
        class_attribute :default_scopes, instance_writer: false, instance_predicate: false
        class_attribute :default_scope_override, instance_predicate: false

        self.default_scopes = []
        self.default_scope_override = nil
      end

      module ClassMethods
        # Returns a scope for the model without the previously set scopes.
        #
        #   class Post < ActiveRecord::Base
        #     def self.default_scope
        #       where(published: true)
        #     end
        #   end
        #
        #   Post.all                                  # Fires "SELECT * FROM posts WHERE published = true"
        #   Post.unscoped.all                         # Fires "SELECT * FROM posts"
        #   Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
        #
        # This method also accepts a block. All queries inside the block will
        # not use the previously set scopes.
        #
        #   Post.unscoped {
        #     Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
        #   }
        def unscoped
          block_given? ? relation.scoping { yield } : relation
        end

        # Are there attributes associated with this scope?
        def scope_attributes? # :nodoc:
          super || default_scopes.any? || respond_to?(:default_scope)
        end

        def before_remove_const #:nodoc:
          self.current_scope = nil
        end

        protected

        # Use this macro in your model to set a default scope for all operations on
        # the model.
        #
        #   class Article < ActiveRecord::Base
        #     default_scope { where(published: true) }
        #   end
        #
        #   Article.all # => SELECT * FROM articles WHERE published = true
        #
        # The #default_scope is also applied while creating/building a record.
        # It is not applied while updating a record.
        #
        #   Article.new.published    # => true
        #   Article.create.published # => true
        #
        # (You can also pass any object which responds to +call+ to the
        # +default_scope+ macro, and it will be called when building the
        # default scope.)
        #
        # If you use multiple #default_scope declarations in your model then
        # they will be merged together:
        #
        #   class Article < ActiveRecord::Base
        #     default_scope { where(published: true) }
        #     default_scope { where(rating: 'G') }
        #   end
        #
        #   Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
        #
        # This is also the case with inheritance and module includes where the
        # parent or module defines a #default_scope and the child or including
        # class defines a second one.
        #
        # If you need to do more complex things with a default scope, you can
        # alternatively define it as a class method:
        #
        #   class Article < ActiveRecord::Base
        #     def self.default_scope
        #       # Should return a scope, you can call 'super' here etc.
        #     end
        #   end
        def default_scope(scope = nil)
          scope = Proc.new if block_given?

          if scope.is_a?(Relation) || !scope.respond_to?(:call)
            raise ArgumentError,
              "Support for calling #default_scope without a block is removed. For example instead " \
              "of `default_scope where(color: 'red')`, please use " \
              "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
              "self.default_scope.)"
          end

          self.default_scopes += [scope]
        end

        def build_default_scope(base_rel = nil) # :nodoc:
          return if abstract_class?

          if self.default_scope_override.nil?
            self.default_scope_override = !Base.is_a?(method(:default_scope).owner)
          end

          if self.default_scope_override
            # The user has defined their own default scope method, so call that
            evaluate_default_scope { default_scope }
          elsif default_scopes.any?
            base_rel ||= relation
            evaluate_default_scope do
              default_scopes.inject(base_rel) do |default_scope, scope|
                default_scope.merge(base_rel.scoping { scope.call })
              end
            end
          end
        end

        def ignore_default_scope? # :nodoc:
          ScopeRegistry.value_for(:ignore_default_scope, base_class)
        end

        def ignore_default_scope=(ignore) # :nodoc:
          ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
        end

        # The ignore_default_scope flag is used to prevent an infinite recursion
        # situation where a default scope references a scope which has a default
        # scope which references a scope...
        def evaluate_default_scope # :nodoc:
          return if ignore_default_scope?

          begin
            self.ignore_default_scope = true
            yield
          ensure
            self.ignore_default_scope = false
          end
        end
      end
    end
  end
end