module Mongoid::Scopable::ClassMethods
Public Instance Methods
Is the class able to have the default scope applied?
@example Can the default scope be applied?
Band.default_scopable?
@return [ true | false ] If the default scope can be applied.
# File lib/mongoid/scopable.rb, line 95 def default_scopable? default_scoping? && !Threaded.without_default_scope?(self) end
Add a default scope to the model. This scope will be applied to all criteria unless unscoped
is specified.
@example Define a default scope with a criteria.
class Band include Mongoid::Document field :active, type: Boolean default_scope where(active: true) end
@example Define a default scope with a proc.
class Band include Mongoid::Document field :active, type: Boolean default_scope ->{ where(active: true) } end
@param [ Proc | Criteria
] value The default scope.
@raise [ Errors::InvalidScope
] If the scope is not a proc or criteria.
@return [ Proc ] The default scope.
# File lib/mongoid/scopable.rb, line 83 def default_scope(value = nil) value = Proc.new { yield } if block_given? check_scope_validity(value) self.default_scoping = process_default_scope(value) end
Get a queryable, either the last one on the scope stack or a fresh one.
@api private
@example Get a queryable.
Model.queryable
@return [ Criteria
] The queryable.
# File lib/mongoid/scopable.rb, line 107 def queryable crit = Threaded.current_scope(self) || Criteria.new(self) crit.embedded = true if (crit.klass.embedded? && !crit.klass.cyclic?) crit end
Create a scope that can be accessed from the class level or chained to criteria by the provided name.
@example Create named scopes.
class Person include Mongoid::Document field :active, type: Boolean field :count, type: Integer scope :active, -> { where(active: true) } scope :at_least, ->(count){ where(:count.gt => count) } end
@param [ Symbol ] name The name of the scope. @param [ Proc ] value The conditions of the scope.
@raise [ Errors::InvalidScope
] If the scope is not a proc. @raise [ Errors::ScopeOverwrite
] If the scope name already exists.
# File lib/mongoid/scopable.rb, line 132 def scope(name, value, &block) normalized = name.to_sym check_scope_validity(value) check_scope_name(normalized) _declared_scopes[normalized] = { scope: value, extension: Module.new(&block) } define_scope_method(normalized) end
Get a criteria for the document with normal scoping.
@example Get the criteria.
Band.scoped(skip: 10)
@note This will force the default scope to be applied.
@param [ Hash ] options Query options for the criteria.
@option options [ Integer ] :skip Optional number of documents to skip. @option options [ Integer ] :limit Optional number of documents to
limit.
@option options [ Array ] :sort Optional sorting options.
@return [ Criteria
] A scoped criteria.
# File lib/mongoid/scopable.rb, line 158 def scoped(options = nil) queryable.scoped(options) end
Returns a hash of all the scopes defined for this class, including scopes defined on ancestor classes.
@example Get the defined scopes for a class
class Band include Mongoid::Document field :active, type: Boolean scope :active, -> { where(active: true) } end Band.scopes
@return [ Hash ] The scopes defined for this class
# File lib/mongoid/scopable.rb, line 51 def scopes defined_scopes = {} ancestors.reverse.each do |klass| if klass.respond_to?(:_declared_scopes) defined_scopes.merge!(klass._declared_scopes) end end defined_scopes.freeze end
Get the criteria without any scoping applied.
@example Get the unscoped criteria.
Band.unscoped
@example Yield to block with no scoping.
Band.unscoped do Band.where(name: "Depeche Mode") end
@note This will force the default scope, as well as any scope applied
using ``.with_scope``, to be removed.
@return [ Criteria
| Object
] The unscoped criteria or result of the
block.
# File lib/mongoid/scopable.rb, line 177 def unscoped if block_given? without_default_scope do with_scope(nil) do yield(self) end end else queryable.unscoped end end
Get a criteria with the default scope applied, if possible.
@example Get a criteria with the default scope.
Model.with_default_scope
@return [ Criteria
] The criteria.
# File lib/mongoid/scopable.rb, line 195 def with_default_scope queryable.with_default_scope end
Pushes the provided criteria onto the scope stack, and removes it after the provided block is yielded.
@example Yield to the criteria.
Person.with_scope(criteria)
@param [ Criteria
] criteria The criteria to apply.
@return [ Criteria
] The yielded criteria.
# File lib/mongoid/scopable.rb, line 209 def with_scope(criteria) previous = Threaded.current_scope(self) Threaded.set_current_scope(criteria, self) begin yield criteria ensure Threaded.set_current_scope(previous, self) end end
Execute the block without applying the default scope.
@example Execute without the default scope.
Band.without_default_scope do Band.where(name: "Depeche Mode") end
@return [ Object
] The result of the block.
# File lib/mongoid/scopable.rb, line 227 def without_default_scope Threaded.begin_without_default_scope(self) yield ensure Threaded.exit_without_default_scope(self) end
Private Instance Methods
Warns or raises exception if overriding another scope or method.
@api private
@example Warn or raise error if name exists.
Model.check_scope_name("test")
@param [ String | Symbol ] name The name of the scope.
@raise [ Errors::ScopeOverwrite
] If the name exists and configured to
raise the error.
# File lib/mongoid/scopable.rb, line 247 def check_scope_name(name) if _declared_scopes[name] || respond_to?(name, true) if Mongoid.scope_overwrite_exception raise Errors::ScopeOverwrite.new(self.name, name) else Mongoid.logger.warn( "Creating scope :#{name} which conflicts with #{self.name}.#{name}. " + "Calls to `Mongoid::Criteria##{name}` will delegate to " + "`Mongoid::Criteria##{name}` for criteria with klass #{self.name} " + "and will ignore the declared scope." ) end end end
Checks if the intended scope is a valid object, either a criteria or proc with a criteria.
@api private
@example Check if the scope is valid.
Model.check_scope_validity({})
@param [ Object
] value The intended scope.
@raise [ Errors::InvalidScope
] If the scope is not a valid object.
# File lib/mongoid/scopable.rb, line 273 def check_scope_validity(value) unless value.respond_to?(:call) raise Errors::InvalidScope.new(self, value) end end
Defines the actual class method that will execute the scope when called.
@api private
@example Define the scope class method.
Model.define_scope_method(:active)
@param [ Symbol ] name The method/scope name.
@return [ Method ] The defined method.
# File lib/mongoid/scopable.rb, line 290 def define_scope_method(name) singleton_class.class_eval do define_method(name) do |*args, **kwargs| scoping = _declared_scopes[name] scope = instance_exec(*args, **kwargs, &scoping[:scope]) extension = scoping[:extension] to_merge = scope || queryable criteria = if Mongoid.allow_scopes_to_unset_default_scope to_merge else to_merge.empty_and_chainable? ? to_merge : with_default_scope.merge(to_merge) end criteria.extend(extension) criteria end end end
Process the default scope value. If one already exists, we merge the new one into the old one.
@api private
@example Process the default scope.
Model.process_default_scope(value)
@param [ Criteria
| Proc ] value The default scope value.
# File lib/mongoid/scopable.rb, line 319 def process_default_scope(value) if existing = default_scoping ->{ existing.call.merge(value.to_proc.call) } else value.to_proc end end