A summary of data about the Ruby ecosystem.

https://github.com/heartcombo/has_scope

Map incoming controller parameters to named scopes in your resources
https://github.com/heartcombo/has_scope

Keywords from Contributors

activerecord activejob mvc devise rubygems rails-helper form-builder dsl controllers flash-messages

Last synced: about 17 hours ago
JSON representation

Repository metadata

Map incoming controller parameters to named scopes in your resources

README.md

HasScope

Gem Version

HasScope allows you to dynamically apply named scopes to your resources based on an incoming set of parameters.

The most common usage is to map incoming controller parameters to named scopes for filtering resources, but it can be used anywhere.

Installation

Add has_scope to your bundle

bundle add has_scope

or add it manually to your Gemfile if you prefer.

gem 'has_scope'

Examples

For the following examples we'll use a model called graduations:

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

Usage 1: Rails Controllers

HasScope exposes the has_scope method automatically in all your controllers. This is used to declare the scopes a controller action can use to filter a resource:

class GraduationsController < ApplicationController
  has_scope :featured, type: :boolean
  has_scope :by_degree
  has_scope :by_period, using: %i[started_at ended_at], type: :hash
end

To apply the scopes to a specific resource, you just need to call apply_scopes:

class GraduationsController < ApplicationController
  has_scope :featured, type: :boolean
  has_scope :by_degree
  has_scope :by_period, using: %i[started_at ended_at], type: :hash

  def index
    @graduations = apply_scopes(Graduation).all
  end
end

Then for each request to the index action, HasScope will automatically apply the scopes as follows:

# GET /graduations
# No scopes applied
#=> brings all graduations
apply_scopes(Graduation).all == Graduation.all

# GET /graduations?featured=true
# The "featured' scope is applied
#=> brings featured graduations
apply_scopes(Graduation).all == Graduation.featured

# GET /graduations?by_period[started_at]=20100701&by_period[ended_at]=20101013
#=> brings graduations in the given period
apply_scopes(Graduation).all == Graduation.by_period('20100701', '20101013')

# GET /graduations?featured=true&by_degree=phd
#=> brings featured graduations with phd degree
apply_scopes(Graduation).all == Graduation.featured.by_degree('phd')

# GET /graduations?finished=true&by_degree=phd
#=> brings only graduations with phd degree because we didn't declare finished in our controller as a permitted scope
apply_scopes(Graduation).all == Graduation.by_degree('phd')

Check for currently applied scopes

HasScope creates a helper method called current_scopes to retrieve all the scopes applied. As it's a helper method, you'll be able to access it in the controller action or the view rendered in that action.

Coming back to one of the examples above:

# GET /graduations?featured=true&by_degree=phd
#=> brings featured graduations with phd degree
apply_scopes(Graduation).all == Graduation.featured.by_degree('phd')

Calling current_scopes after apply_scopes in the controller action or view would return the following:

current_scopes
#=> { featured: true, by_degree: 'phd' }

Usage 2: Standalone Mode

HasScope can also be used in plain old Ruby objects (PORO). To implement the previous example using this approach, create a bare object and include HasScope to get access to its features:

Note: We'll create a simple version of a query object for this example as this type of object can have multiple different implementations.

class GraduationsSearchQuery
  include HasScope
  # ...
end

Next, declare the scopes to be used the same way:

class GraduationsSearchQuery
  include HasScope

  has_scope :featured, type: :boolean
  has_scope :by_degree
  has_scope :by_period, using: %i[started_at ended_at], type: :hash
  # ...
end

Now, allow your object to perform the query by exposing a method that will use apply_scopes:

class GraduationsSearchQuery
  include HasScope

  has_scope :featured, type: :boolean
  has_scope :by_degree
  has_scope :by_period, using: %i[started_at ended_at], type: :hash

  def perform(collection: Graduation, params: {})
    apply_scopes(collection, params)
  end
end

Note that apply_scopes receives a Hash as a second argument, which represents the incoming params that determine which scopes should be applied to the model/collection. It defaults to params for compatibility with controllers, which is why it's not necessary to pass that second argument in the controller context.

Now in your controller you can call the GraduationsSearchQuery with the incoming parameters from the controller:

class GraduationsController < ApplicationController
  def index
    graduations_query = GraduationsSearchQuery.new
    @graduations = graduations_query.perform(collection: Graduation, params: params)
  end
end

Accessing current_scopes

In the controller context, current_scopes is made available as a helper method to the controller and view, but it's a protected method of HasScope's implementation, to prevent it from becoming publicly accessible outside of HasScope itself. This means that the object implementation showed above has access to current_scopes internally, but it's not exposed to other objects that interact with it.

If you need to access current_scopes elsewhere, you can change the method visibility like so:

class GraduationsSearchQuery
  include HasScope

  # ...

  public :current_scopes

  # ...
end

Options

has_scope 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.

  • :in - A shortcut for combining the :using option with nested hashes.

  • :if - Specifies a method or proc to call to determine if the scope should apply. Passing a string is deprecated and it will be removed in a future version.

  • :unless - Specifies a method or proc to call to determine if the scope should NOT apply. Passing a string is deprecated and it will be removed in a future version.

  • :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.

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
any usual scope.

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) }

Note: it is not possible to apply a boolean scope with just the query param being present, e.g.
?active, that's not considered a "true" value (the param value will be nil), and thus the
scope will be called with false as argument. In order for the scope to receive a true argument
the param value must be set to one of the "true" values above, e.g. ?active=true or ?active=1.

Block usage

has_scope also accepts a block in case we need to manipulate the given value and/or call the scope in some custom way. Usually three arguments are passed to the block:

  • The instance of the controller or object where it's included
  • The current scope chain
  • The value of the scope to apply

💡 We suggest you name the first argument depending on how you're using HasScope. If it's the controller, use the word "controller". If it's a query object for example, use "query", or something meaningful for that context (or simply use "context"). In the following examples, we'll use controller for simplicity.

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:

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:

# 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:

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.

Check which scopes have been applied

To check which scopes have been applied, you can call current_scopes from the controller or view.
This returns a hash with the scope name as the key and the scope value as the value.

For example, if a boolean :active scope has been applied, current_scopes will return { active: true }.

Supported Ruby / Rails versions

We intend to maintain support for all Ruby / Rails versions that haven't reached end-of-life.

For more information about specific versions please check Ruby
and Rails maintenance policies, and our test matrix.

Bugs and Feedback

If you discover any bugs or want to drop a line, feel free to create an issue on GitHub.

License

MIT License.
Copyright 2020-2025 Rafael França, Carlos Antonio da Silva.
Copyright 2009-2019 Plataformatec.


Owner metadata


GitHub Events

Total
Last Year

Committers metadata

Last synced: 8 days ago

Total Commits: 203
Total Committers: 38
Avg Commits per committer: 5.342
Development Distribution Score (DDS): 0.542

Commits in past year: 8
Committers in past year: 1
Avg Commits per committer in past year: 8.0
Development Distribution Score (DDS) in past year: 0.0

Name Email Commits
Carlos Antonio da Silva c****a@g****m 93
José Valim j****m@g****m 25
Lucas Mazza l****a@p****r 12
Rafael Mendonça França r****a@g****m 9
Yury Velikanau y****u@g****m 8
Joe Francis j****e@l****m 6
Marcos Ferreira m****f@g****m 5
Ben Kreeger b****n@k****r 5
Tobias Maier i****o@t****o 3
Jack Dempsey j****y@g****m 3
Rafael Mendonça França r****a@p****r 3
Krzysztof Herod k****d@x****m 2
Leonardo Tegon l****n@p****r 2
Max Melentiev m****m@g****m 2
Max Schwenk m****k@g****m 2
Chris Keele d****v@c****m 1
Atul Bhosale a****e@g****m 1
Bruno Casali b****i@g****m 1
Dhyego Fernando d****o@g****m 1
pinzonjulian j****a@g****m 1
iain i****n@i****l 1
fabianoarruda f****a@g****m 1
beerlington p****e@l****s 1
Yves Senn y****n@g****m 1
Vasiliy Ermolovich y****h@g****m 1
Tyler Rick t****r@t****m 1
Thomas Floyd Wright t****t@g****m 1
Salimane Adjao Moustapha me@s****m 1
Peter Suschlik p****r@s****e 1
Peter Goldstein p****n@g****m 1
and 8 more...

Committer domains:


Issue and Pull Request metadata

Last synced: 2 months ago

Total issues: 56
Total pull requests: 48
Average time to close issues: 8 months
Average time to close pull requests: 4 months
Total issue authors: 52
Total pull request authors: 38
Average comments per issue: 2.82
Average comments per pull request: 2.46
Merged pull request: 28
Bot issues: 0
Bot pull requests: 0

Past year issues: 0
Past year pull requests: 3
Past year average time to close issues: N/A
Past year average time to close pull requests: 19 minutes
Past year issue authors: 0
Past year pull request authors: 2
Past year average comments per issue: 0
Past year average comments per pull request: 0.0
Past year merged pull request: 1
Past year bot issues: 0
Past year bot pull requests: 0

More stats: https://issues.ecosyste.ms/repositories/lookup?url=https://github.com/heartcombo/has_scope

Top Issue Authors

  • femto (3)
  • ausminternet (2)
  • kstevens715 (2)
  • Altonymous (1)
  • OssieHirst (1)
  • fabianoarruda (1)
  • GuyPaddock (1)
  • zmillman (1)
  • hschne (1)
  • alaz (1)
  • alecvn (1)
  • narutoo9x (1)
  • tjoyal (1)
  • bnussey (1)
  • hrdwdmrbl (1)

Top Pull Request Authors

  • carlosantoniodasilva (3)
  • lucasmazza (3)
  • j3pic (3)
  • printercu (2)
  • christhekeele (2)
  • kreeger (2)
  • jusleg (2)
  • dbalexandre (1)
  • stevenhan2 (1)
  • varyonic (1)
  • gobijan (1)
  • mracos (1)
  • GuyPaddock (1)
  • iain (1)
  • krzysiekherod (1)

Top Issue Labels

Top Pull Request Labels

  • Needs review (1)

Package metadata

gem.coop: has_scope

Maps controller filters to your resource scopes

  • Homepage: https://github.com/heartcombo/has_scope
  • Documentation: http://www.rubydoc.info/gems/has_scope/
  • Licenses: MIT
  • Latest release: 0.9.0 (published 2 months ago)
  • Last Synced: 2025-12-09T01:32:18.648Z (3 days ago)
  • Versions: 17
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 61,570,503 Total
  • Docker Downloads: 25,859
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Downloads: 0.448%
    • Average: 0.565%
    • Docker downloads count: 1.811%
  • Maintainers (2)
rubygems.org: has_scope

Maps controller filters to your resource scopes

  • Homepage: https://github.com/heartcombo/has_scope
  • Documentation: http://www.rubydoc.info/gems/has_scope/
  • Licenses: MIT
  • Latest release: 0.9.0 (published 2 months ago)
  • Last Synced: 2025-12-08T16:31:53.689Z (4 days ago)
  • Versions: 17
  • Dependent Packages: 36
  • Dependent Repositories: 19,707
  • Downloads: 61,555,339 Total
  • Docker Downloads: 25,859
  • Rankings:
    • Dependent repos count: 0.258%
    • Downloads: 0.437%
    • Dependent packages count: 0.699%
    • Average: 1.238%
    • Stargazers count: 1.251%
    • Docker downloads count: 2.055%
    • Forks count: 2.727%
  • Maintainers (2)
proxy.golang.org: github.com/heartcombo/has_scope


Dependencies

.github/workflows/test.yml actions
  • actions/checkout v2 composite
  • ruby/setup-ruby v1 composite
Gemfile rubygems
  • actionpack ~> 7.0.0
  • activesupport ~> 7.0.0
Gemfile.lock rubygems
  • actionpack 7.0.4
  • actionview 7.0.4
  • activesupport 7.0.4
  • builder 3.2.4
  • concurrent-ruby 1.1.10
  • crass 1.0.6
  • erubi 1.11.0
  • has_scope 0.8.0
  • i18n 1.12.0
  • loofah 2.19.0
  • metaclass 0.0.4
  • mini_portile2 2.8.0
  • minitest 5.16.3
  • mocha 1.0.0
  • nokogiri 1.13.9
  • racc 1.6.0
  • rack 2.2.4
  • rack-test 2.0.2
  • rails-dom-testing 2.0.3
  • rails-html-sanitizer 1.4.3
  • rake 13.0.6
  • tzinfo 2.0.5
has_scope.gemspec rubygems
  • mocha ~> 1.0.0 development
  • rake >= 0 development
  • actionpack >= 5.2
  • activesupport >= 5.2

Score: 29.730821179852605