aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/default_scope.rb
blob: dd637ad6c5f2da9037047f16b5ae5582515463e8 (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
require 'active_support/concern'

module ActiveRecord
  module DefaultScope
    extend ActiveSupport::Concern

    included do
      # Stores the default scope for the class
      class_attribute :default_scopes, :instance_writer => false
      self.default_scopes = []
    end

    module ClassMethods
      # Returns a scope for this class without taking into account the default_scope.
      #
      #   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"
      #
      # This method also accepts a block meaning that all queries inside the block will
      # not use the default_scope:
      #
      #   Post.unscoped {
      #     Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
      #   }
      #
      # It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
      # does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
      #
      # Post.unscoped.published
      # Post.published
      def unscoped #:nodoc:
        block_given? ? relation.scoping { yield } : relation
      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 <tt>default_scope</tt> 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 use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
      #
      #   class Article < ActiveRecord::Base
      #     default_scope { where(:published_at => Time.now - 1.week) }
      #   end
      #
      # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
      # macro, and it will be called when building the default scope.)
      #
      # If you use multiple <tt>default_scope</tt> 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 <tt>default_scope</tt> 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 = {})
        scope = Proc.new if block_given?
        self.default_scopes = default_scopes + [scope]
      end

      def build_default_scope #:nodoc:
        if method(:default_scope).owner != DefaultScope::ClassMethods
          evaluate_default_scope { default_scope }
        elsif default_scopes.any?
          evaluate_default_scope do
            default_scopes.inject(relation) do |default_scope, scope|
              if scope.is_a?(Hash)
                default_scope.apply_finder_options(scope)
              elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
                default_scope.merge(scope.call)
              else
                default_scope.merge(scope)
              end
            end
          end
        end
      end

      def ignore_default_scope? #:nodoc:
        Thread.current["#{self}_ignore_default_scope"]
      end

      def ignore_default_scope=(ignore) #:nodoc:
        Thread.current["#{self}_ignore_default_scope"] = 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
        return if ignore_default_scope?

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

    end
  end
end