has-scope-0.7.2/ 0000755 0001750 0001750 00000000000 13467464770 012760 5 ustar samyak samyak has-scope-0.7.2/lib/ 0000755 0001750 0001750 00000000000 13467464770 013526 5 ustar samyak samyak has-scope-0.7.2/lib/has_scope.rb 0000644 0001750 0001750 00000015221 13467464770 016020 0 ustar samyak samyak module HasScope
TRUE_VALUES = ["true", true, "1", 1]
ALLOWED_TYPES = {
:array => [[ Array ]],
:hash => [[Hash, ActionController::Parameters]],
:boolean => [[ Object ], -> v { TRUE_VALUES.include?(v) }],
:default => [[ String, Numeric ]],
}
def self.included(base)
base.class_eval do
extend ClassMethods
class_attribute :scopes_configuration, :instance_writer => false
self.scopes_configuration = {}
end
end
module ClassMethods
# Detects params from url and apply as scopes to your classes.
#
# == Options
#
# * :type - Checks the type of the parameter sent. If set to :boolean
# it just calls the named scope, without any argument. By default,
# it does not allow hashes or arrays to be given, except if type
# :hash or :array are set.
#
# * :only - In which actions the scope is applied. By default is :all.
#
# * :except - In which actions the scope is not applied. By default is :none.
#
# * :as - The key in the params hash expected to find the scope.
# Defaults to the scope name.
#
# * :using - If type is a hash, you can provide :using to convert the hash to
# a named scope call with several arguments.
#
# * :if - Specifies a method, proc or string to call to determine
# if the scope should apply
#
# * :unless - Specifies a method, proc or string to call to determine
# if the scope should NOT apply.
#
# * :default - Default value for the scope. Whenever supplied the scope
# is always called.
#
# * :allow_blank - Blank values are not sent to scopes by default. Set to true to overwrite.
#
# == Block usage
#
# has_scope also accepts a block. The controller, current scope and value are yielded
# to the block so the user can apply the scope on its own. This is useful in case we
# need to manipulate the given value:
#
# has_scope :category do |controller, scope, value|
# value != "all" ? scope.by_category(value) : scope
# end
#
# has_scope :not_voted_by_me, :type => :boolean do |controller, scope|
# scope.not_voted_by(controller.current_user.id)
# end
#
def has_scope(*scopes, &block)
options = scopes.extract_options!
options.symbolize_keys!
options.assert_valid_keys(:type, :only, :except, :if, :unless, :default, :as, :using, :allow_blank, :in)
if options.key?(:in)
options[:as] = options[:in]
options[:using] = scopes
end
if options.key?(:using)
if options.key?(:type) && options[:type] != :hash
raise "You cannot use :using with another :type different than :hash"
else
options[:type] = :hash
end
options[:using] = Array(options[:using])
end
options[:only] = Array(options[:only])
options[:except] = Array(options[:except])
self.scopes_configuration = scopes_configuration.dup
scopes.each do |scope|
scopes_configuration[scope] ||= { :as => scope, :type => :default, :block => block }
scopes_configuration[scope] = self.scopes_configuration[scope].merge(options)
end
end
end
protected
# Receives an object where scopes will be applied to.
#
# class GraduationsController < InheritedResources::Base
# has_scope :featured, :type => true, :only => :index
# has_scope :by_degree, :only => :index
#
# def index
# @graduations = apply_scopes(Graduation).all
# end
# end
#
def apply_scopes(target, hash=params)
scopes_configuration.each do |scope, options|
next unless apply_scope_to_action?(options)
key = options[:as]
if hash.key?(key)
value, call_scope = hash[key], true
elsif options.key?(:default)
value, call_scope = options[:default], true
if value.is_a?(Proc)
value = value.arity == 0 ? value.call : value.call(self)
end
end
value = parse_value(options[:type], key, value)
value = normalize_blanks(value)
if call_scope && (value.present? || options[:allow_blank])
current_scopes[key] = value
target = call_scope_by_type(options[:type], scope, target, value, options)
end
end
target
end
# Set the real value for the current scope if type check.
def parse_value(type, key, value) #:nodoc:
klasses, parser = ALLOWED_TYPES[type]
if klasses.any? { |klass| value.is_a?(klass) }
parser ? parser.call(value) : value
end
end
# Screens pseudo-blank params.
def normalize_blanks(value) #:nodoc:
case value
when Array
value.select { |v| v.present? }
when Hash
value.select { |k, v| normalize_blanks(v).present? }.with_indifferent_access
when ActionController::Parameters
normalize_blanks(value.to_unsafe_h)
else
value
end
end
# Call the scope taking into account its type.
def call_scope_by_type(type, scope, target, value, options) #:nodoc:
block = options[:block]
if type == :boolean && !options[:allow_blank]
block ? block.call(self, target) : target.send(scope)
elsif value && options.key?(:using)
value = value.values_at(*options[:using])
block ? block.call(self, target, value) : target.send(scope, *value)
else
block ? block.call(self, target, value) : target.send(scope, value)
end
end
# Given an options with :only and :except arrays, check if the scope
# can be performed in the current action.
def apply_scope_to_action?(options) #:nodoc:
return false unless applicable?(options[:if], true) && applicable?(options[:unless], false)
if options[:only].empty?
options[:except].empty? || !options[:except].include?(action_name.to_sym)
else
options[:only].include?(action_name.to_sym)
end
end
# Evaluates the scope options :if or :unless. Returns true if the proc
# method, or string evals to the expected value.
def applicable?(string_proc_or_symbol, expected) #:nodoc:
case string_proc_or_symbol
when String
eval(string_proc_or_symbol) == expected
when Proc
string_proc_or_symbol.call(self) == expected
when Symbol
send(string_proc_or_symbol) == expected
else
true
end
end
# Returns the scopes used in this action.
def current_scopes
@current_scopes ||= {}
end
end
ActiveSupport.on_load :action_controller do
include HasScope
helper_method :current_scopes if respond_to?(:helper_method)
end
has-scope-0.7.2/lib/has_scope/ 0000755 0001750 0001750 00000000000 13467464770 015472 5 ustar samyak samyak has-scope-0.7.2/lib/has_scope/version.rb 0000644 0001750 0001750 00000000050 13467464770 017477 0 ustar samyak samyak module HasScope
VERSION = "0.7.2"
end
has-scope-0.7.2/test/ 0000755 0001750 0001750 00000000000 13467464770 013737 5 ustar samyak samyak has-scope-0.7.2/test/has_scope_test.rb 0000644 0001750 0001750 00000027733 13467464770 017303 0 ustar samyak samyak require 'test_helper'
HasScope::ALLOWED_TYPES[:date] = [[String], -> v { Date.parse(v) rescue nil }]
class Tree; end
class TreesController < ApplicationController
has_scope :color, :unless => :show_all_colors?
has_scope :only_tall, :type => :boolean, :only => :index, :if => :restrict_to_only_tall_trees?
has_scope :shadown_range, :default => 10, :except => [ :index, :show, :new ]
has_scope :root_type, :as => :root, :allow_blank => true
has_scope :planted_before, :default => proc { Date.today }
has_scope :planted_after, :type => :date
has_scope :calculate_height, :default => proc {|c| c.session[:height] || 20 }, :only => :new
has_scope :paginate, :type => :hash
has_scope :args_paginate, :type => :hash, :using => [:page, :per_page]
has_scope :categories, :type => :array
has_scope :title, :in => :q
has_scope :content, :in => :q
has_scope :conifer, type: :boolean, :allow_blank => true
has_scope :only_short, :type => :boolean do |controller, scope|
scope.only_really_short!(controller.object_id)
end
has_scope :by_category do |controller, scope, value|
scope.by_given_category(controller.object_id, value + "_id")
end
def index
@trees = apply_scopes(Tree).all
end
def new
@tree = apply_scopes(Tree).new
end
def show
@tree = apply_scopes(Tree).find(params[:id])
end
alias :edit :show
protected
def restrict_to_only_tall_trees?
true
end
def show_all_colors?
false
end
if ActionPack::VERSION::MAJOR == 5
def default_render
render body: action_name
end
else
# TODO: Remove this when we only support Rails 5.
def default_render
render text: action_name
end
end
end
class BonsaisController < TreesController
has_scope :categories, :if => :categories?
protected
def categories?
false
end
end
class HasScopeTest < ActionController::TestCase
tests TreesController
def test_boolean_scope_is_called_when_boolean_param_is_true
Tree.expects(:only_tall).with().returns(Tree).in_sequence
Tree.expects(:all).returns([mock_tree]).in_sequence
get :index, :only_tall => 'true'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :only_tall => true }, current_scopes)
end
def test_boolean_scope_is_not_called_when_boolean_param_is_false
Tree.expects(:only_tall).never
Tree.expects(:all).returns([mock_tree])
get :index, :only_tall => 'false'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_boolean_scope_with_allow_blank_is_called_when_boolean_param_is_true
Tree.expects(:conifer).with(true).returns(Tree).in_sequence
Tree.expects(:all).returns([mock_tree]).in_sequence
get :index, :conifer => 'true'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :conifer => true }, current_scopes)
end
def test_boolean_scope_with_allow_blank_is_called_when_boolean_param_is_false
Tree.expects(:conifer).with(false).returns(Tree).in_sequence
Tree.expects(:all).returns([mock_tree]).in_sequence
get :index, :conifer => 'not_true'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :conifer => false }, current_scopes)
end
def test_boolean_scope_with_allow_blank_is_not_called_when_boolean_param_is_not_present
Tree.expects(:conifer).never
Tree.expects(:all).returns([mock_tree])
get :index
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_is_called_only_on_index
Tree.expects(:only_tall).never
Tree.expects(:find).with('42').returns(mock_tree)
get :show, :only_tall => 'true', :id => '42'
assert_equal(mock_tree, assigns(:@tree))
assert_equal({ }, current_scopes)
end
def test_scope_is_skipped_when_if_option_is_false
@controller.stubs(:restrict_to_only_tall_trees?).returns(false)
Tree.expects(:only_tall).never
Tree.expects(:all).returns([mock_tree])
get :index, :only_tall => 'true'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_is_skipped_when_unless_option_is_true
@controller.stubs(:show_all_colors?).returns(true)
Tree.expects(:color).never
Tree.expects(:all).returns([mock_tree])
get :index, :color => 'blue'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_is_called_except_on_index
Tree.expects(:shadown_range).never
Tree.expects(:all).returns([mock_tree])
get :index, :shadown_range => 20
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_is_called_with_arguments
Tree.expects(:color).with('blue').returns(Tree).in_sequence
Tree.expects(:all).returns([mock_tree]).in_sequence
get :index, :color => 'blue'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :color => 'blue' }, current_scopes)
end
def test_scope_is_not_called_if_blank
Tree.expects(:color).never
Tree.expects(:all).returns([mock_tree]).in_sequence
get :index, :color => ''
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_is_called_when_blank_if_allow_blank_is_given
Tree.expects(:root_type).with('').returns(Tree)
Tree.expects(:all).returns([mock_tree]).in_sequence
get :index, :root => ''
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :root => '' }, current_scopes)
end
def test_multiple_scopes_are_called
Tree.expects(:only_tall).with().returns(Tree)
Tree.expects(:color).with('blue').returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, :color => 'blue', :only_tall => 'true'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :color => 'blue', :only_tall => true }, current_scopes)
end
def test_scope_of_type_hash
hash = { "page" => "1", "per_page" => "10" }
Tree.expects(:paginate).with(hash).returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, :paginate => hash
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ paginate: hash }, current_scopes)
end
def test_scope_of_type_hash_with_using
hash = { "page" => "1", "per_page" => "10" }
Tree.expects(:args_paginate).with("1", "10").returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, :args_paginate => hash
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :args_paginate => hash }, current_scopes)
end
def test_hash_with_blank_values_is_ignored
hash = { "page" => "", "per_page" => "" }
Tree.expects(:paginate).never
Tree.expects(:all).returns([mock_tree])
get :index, :paginate => hash
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_nested_hash_with_blank_values_is_ignored
hash = { "parent" => {"children" => ""} }
Tree.expects(:paginate).never
Tree.expects(:all).returns([mock_tree])
get :index, :paginate => hash
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_nested_blank_array_param_is_ignored
hash = { "parent" => [""] }
Tree.expects(:paginate).never
Tree.expects(:all).returns([mock_tree])
get :index, :paginate => hash
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_of_type_array
array = %w(book kitchen sport)
Tree.expects(:categories).with(array).returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, :categories => array
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :categories => array }, current_scopes)
end
def test_array_of_blank_values_is_ignored
Tree.expects(:categories).never
Tree.expects(:all).returns([mock_tree])
get :index, :categories => [""]
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_of_invalid_type_silently_fails
Tree.expects(:all).returns([mock_tree])
get :index, :paginate => "1"
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ }, current_scopes)
end
def test_scope_is_called_with_default_value
Tree.expects(:shadown_range).with(10).returns(Tree).in_sequence
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
get :edit, :id => '42'
assert_equal(mock_tree, assigns(:@tree))
assert_equal({ :shadown_range => 10 }, current_scopes)
end
def test_default_scope_value_can_be_overwritten
Tree.expects(:shadown_range).with('20').returns(Tree).in_sequence
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
get :edit, :id => '42', :shadown_range => '20'
assert_equal(mock_tree, assigns(:@tree))
assert_equal({ :shadown_range => '20' }, current_scopes)
end
def test_scope_with_different_key
Tree.expects(:root_type).with('outside').returns(Tree).in_sequence
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
get :show, :id => '42', :root => 'outside'
assert_equal(mock_tree, assigns(:@tree))
assert_equal({ :root => 'outside' }, current_scopes)
end
def test_scope_with_default_value_as_a_proc_without_argument
Date.expects(:today).returns("today")
Tree.expects(:planted_before).with("today").returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :planted_before => "today" }, current_scopes)
end
def test_scope_with_default_value_as_proc_with_argument
session[:height] = 100
Tree.expects(:calculate_height).with(100).returns(Tree).in_sequence
Tree.expects(:new).returns(mock_tree).in_sequence
get :new
assert_equal(mock_tree, assigns(:@tree))
assert_equal({ :calculate_height => 100 }, current_scopes)
end
def test_scope_with_custom_type
parsed = Date.civil(2014,11,11)
Tree.expects(:planted_after).with(parsed).returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, :planted_after => "2014-11-11"
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :planted_after => parsed }, current_scopes)
end
def test_scope_with_boolean_block
Tree.expects(:only_really_short!).with(@controller.object_id).returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, :only_short => 'true'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :only_short => true }, current_scopes)
end
def test_scope_with_other_block_types
Tree.expects(:by_given_category).with(@controller.object_id, 'for_id').returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, :by_category => 'for'
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ :by_category => 'for' }, current_scopes)
end
def test_scope_with_nested_hash_and_in_option
hash = { 'title' => 'the-title', 'content' => 'the-content' }
Tree.expects(:title).with('the-title').returns(Tree)
Tree.expects(:content).with('the-content').returns(Tree)
Tree.expects(:all).returns([mock_tree])
get :index, q: hash
assert_equal([mock_tree], assigns(:@trees))
assert_equal({ q: hash }, current_scopes)
end
def test_overwritten_scope
assert_nil(TreesController.scopes_configuration[:categories][:if])
assert_equal(:categories?, BonsaisController.scopes_configuration[:categories][:if])
end
protected
if ActionPack::VERSION::MAJOR == 5
# TODO: Remove this when we only support Rails 5.
def get(action, params = {})
super action, params: params
end
end
def mock_tree(stubs={})
@mock_tree ||= mock(stubs)
end
def current_scopes
@controller.send :current_scopes
end
def assigns(ivar)
@controller.instance_variable_get(ivar)
end
end
class TreeHugger
include HasScope
has_scope :color
def by_color
apply_scopes(Tree, :color => 'blue')
end
end
class HasScopeOutsideControllerTest < ActiveSupport::TestCase
def test_has_scope_usable_outside_controller
Tree.expects(:color).with('blue')
TreeHugger.new.by_color
end
end
has-scope-0.7.2/test/test_helper.rb 0000644 0001750 0001750 00000001315 13467464770 016602 0 ustar samyak samyak require 'bundler/setup'
require 'minitest/autorun'
require 'mocha'
require 'mocha/mini_test'
# Configure Rails
ENV['RAILS_ENV'] = 'test'
require 'active_support'
require 'active_support/core_ext/string/strip'
require 'action_controller'
require 'action_dispatch/middleware/flash'
$:.unshift File.expand_path('../../lib', __FILE__)
require 'has_scope'
HasScope::Routes = ActionDispatch::Routing::RouteSet.new
HasScope::Routes.draw do
get '/:controller(/:action(/:id))'
end
class ApplicationController < ActionController::Base
include HasScope::Routes.url_helpers
end
class ActiveSupport::TestCase
self.test_order = :random if respond_to?(:test_order=)
setup do
@routes = HasScope::Routes
end
end
has-scope-0.7.2/MIT-LICENSE 0000644 0001750 0001750 00000002114 13467464770 014412 0 ustar samyak samyak Copyright 2009-2017 Plataforma Tecnologia. http://blog.plataformatec.com.br
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
has-scope-0.7.2/has_scope.gemspec 0000644 0001750 0001750 00000004200 13467464770 016265 0 ustar samyak samyak #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: has_scope 0.7.2 ruby lib
Gem::Specification.new do |s|
s.name = "has_scope".freeze
s.version = "0.7.2"
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
s.authors = ["Jos\u00E9 Valim".freeze]
s.date = "2018-04-10"
s.description = "Maps controller filters to your resource scopes".freeze
s.email = "opensource@plataformatec.com.br".freeze
s.extra_rdoc_files = ["README.md".freeze]
s.files = ["MIT-LICENSE".freeze, "README.md".freeze, "lib/has_scope.rb".freeze, "lib/has_scope/version.rb".freeze, "test/has_scope_test.rb".freeze, "test/test_helper.rb".freeze]
s.homepage = "http://github.com/plataformatec/has_scope".freeze
s.licenses = ["MIT".freeze]
s.rdoc_options = ["--charset=UTF-8".freeze]
s.required_ruby_version = Gem::Requirement.new(">= 2.1.7".freeze)
s.rubygems_version = "2.7.6.2".freeze
s.summary = "Maps controller filters to your resource scopes.".freeze
s.test_files = ["test/has_scope_test.rb".freeze, "test/test_helper.rb".freeze]
if s.respond_to? :specification_version then
s.specification_version = 4
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q.freeze, [">= 4.1"])
s.add_runtime_dependency(%q.freeze, [">= 4.1"])
s.add_development_dependency(%q.freeze, ["~> 1.0.0"])
s.add_development_dependency(%q.freeze, [">= 0"])
else
s.add_dependency(%q.freeze, [">= 4.1"])
s.add_dependency(%q.freeze, [">= 4.1"])
s.add_dependency(%q.freeze, ["~> 1.0.0"])
s.add_dependency(%q.freeze, [">= 0"])
end
else
s.add_dependency(%q.freeze, [">= 4.1"])
s.add_dependency(%q.freeze, [">= 4.1"])
s.add_dependency(%q.freeze, ["~> 1.0.0"])
s.add_dependency(%q.freeze, [">= 0"])
end
end
has-scope-0.7.2/README.md 0000644 0001750 0001750 00000012041 13467464770 014235 0 ustar samyak samyak ## HasScope
[](http://badge.fury.io/rb/has_scope)
[](http://travis-ci.org/plataformatec/has_scope)
[](https://codeclimate.com/github/plataformatec/has_scope)
Has scope allows you to map incoming controller parameters to named scopes in your resources.
Imagine the following model called graduations:
```ruby
class Graduation < ActiveRecord::Base
scope :featured, -> { where(:featured => true) }
scope :by_degree, -> degree { where(:degree => degree) }
scope :by_period, -> started_at, ended_at { where("started_at = ? AND ended_at = ?", started_at, ended_at) }
end
```
You can use those named scopes as filters by declaring them on your controller:
```ruby
class GraduationsController < ApplicationController
has_scope :featured, :type => :boolean
has_scope :by_degree
end
```
Now, if you want to apply them to an specific resource, you just need to call `apply_scopes`:
```ruby
class GraduationsController < ApplicationController
has_scope :featured, :type => :boolean
has_scope :by_degree
has_scope :by_period, :using => [:started_at, :ended_at], :type => :hash
def index
@graduations = apply_scopes(Graduation).all
end
end
```
Then for each request:
```
/graduations
#=> acts like a normal request
/graduations?featured=true
#=> calls the named scope and bring featured graduations
/graduations?by_period[started_at]=20100701&by_period[ended_at]=20101013
#=> brings graduations in the given period
/graduations?featured=true&by_degree=phd
#=> brings featured graduations with phd degree
```
You can retrieve all the scopes applied in one action with `current_scopes` method.
In the last case, it would return: { :featured => true, :by_degree => "phd" }.
## Installation
Add `has_scope` to your Gemfile or install it from Rubygems.
```ruby
gem 'has_scope'
```
## Options
HasScope supports several options:
* `:type` - Checks the type of the parameter sent.
By default, it does not allow hashes or arrays to be given,
except if type `:hash` or `:array` are set.
Symbols are never permitted to prevent memory leaks, so ensure any routing
constraints you have that add parameters use string values.
* `:only` - In which actions the scope is applied.
* `:except` - In which actions the scope is not applied.
* `:as` - The key in the params hash expected to find the scope. Defaults to the scope name.
* `:using` - The subkeys to be used as args when type is a hash.
* `:if` - Specifies a method, proc or string to call to determine if the scope should apply.
* `:unless` - Specifies a method, proc or string to call to determine if the scope should NOT apply.
* `:default` - Default value for the scope. Whenever supplied the scope is always called.
* `:allow_blank` - Blank values are not sent to scopes by default. Set to true to overwrite.
* `:in` - A shortcut for combining the `:using` option with nested hashes.
## Boolean usage
If `type: :boolean` is set it just calls the named scope, without any arguments, when parameter
is set to a "true" value. `'true'` and `'1'` are parsed as `true`, everything else as `false`.
When boolean scope is set up with `allow_blank: true`, it will call the scope
with the value as usual scope.
```ruby
has_scope :visible, type: :boolean
has_scope :active, type: :boolean, allow_blank: true
# and models with
scope :visible, -> { where(visible: true) }
scope :active, ->(value = true) { where(active: value) }
```
## Block usage
`has_scope` also accepts a block. The controller, current scope and value are yielded
to the block so the user can apply the scope on its own. This is useful in case we
need to manipulate the given value:
```ruby
has_scope :category do |controller, scope, value|
value != "all" ? scope.by_category(value) : scope
end
```
When used with booleans without `:allow_blank`, it just receives two arguments
and is just invoked if true is given:
```ruby
has_scope :not_voted_by_me, :type => :boolean do |controller, scope|
scope.not_voted_by(controller.current_user.id)
end
```
## Keyword arguments
Scopes with keyword arguments need to be called in a block:
```ruby
# in the model
scope :for_course, lambda { |course_id:| where(course_id: course_id) }
# in the controller
has_scope :for_course do |controller, scope, value|
scope.for_course(course_id: value)
end
```
## Apply scope on every request
To apply scope on every request set default value and `allow_blank: true`:
```ruby
has_scope :available, default: nil, allow_blank: true, only: :show, unless: :admin?
# model:
scope :available, ->(*) { where(blocked: false) }
```
This will allow usual users to get only available items, but admins will
be able to access blocked items too.
## Bugs and Feedback
If you discover any bugs or want to drop a line, feel free to create an issue on GitHub.
http://github.com/plataformatec/has_scope/issues
MIT License. Copyright 2009-2016 Plataformatec. http://blog.plataformatec.com.br