aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/scoping/default.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/scoping/default.rb')
-rw-r--r--activerecord/lib/active_record/scoping/default.rb159
1 files changed, 159 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
new file mode 100644
index 0000000000..6caf9b3251
--- /dev/null
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: true
+
+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, default: []
+ class_attribute :default_scope_override, instance_writer: false, instance_predicate: false, default: 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? ? _scoping(relation) { yield } : relation
+ end
+
+ def _scoping(relation) # :nodoc:
+ previous, self.current_scope = current_scope(true), relation
+ yield
+ ensure
+ self.current_scope = previous
+ 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
+
+ private
+
+ # 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) # :doc:
+ 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)
+ return if abstract_class?
+
+ if default_scope_override.nil?
+ self.default_scope_override = !Base.is_a?(method(:default_scope).owner)
+ end
+
+ if default_scope_override
+ # The user has defined their own default scope method, so call that
+ evaluate_default_scope do
+ if scope = default_scope
+ (base_rel ||= relation).merge!(scope)
+ end
+ end
+ elsif default_scopes.any?
+ base_rel ||= relation
+ evaluate_default_scope do
+ default_scopes.inject(base_rel) do |default_scope, scope|
+ scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
+ default_scope.merge!(base_rel.instance_exec(&scope))
+ end
+ end
+ end
+ end
+
+ def ignore_default_scope?
+ ScopeRegistry.value_for(:ignore_default_scope, base_class)
+ end
+
+ def ignore_default_scope=(ignore)
+ 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
+ return if ignore_default_scope?
+
+ begin
+ self.ignore_default_scope = true
+ yield
+ ensure
+ self.ignore_default_scope = false
+ end
+ end
+ end
+ end
+ end
+end