Repository: varvet/pundit
Branch: main
Commit: 06318683c960
Files: 92
Total size: 132.4 KB
Directory structure:
gitextract_wfsck2t6/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE/
│ │ └── gem_release_template.md
│ ├── pull_request_template.md
│ └── workflows/
│ ├── main.yml
│ └── push_gem.yml
├── .gitignore
├── .rubocop_ignore_git.yml
├── .standard.yml
├── .yardopts
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── SECURITY.md
├── bin/
│ ├── console
│ └── setup
├── config/
│ └── rubocop-rspec.yml
├── lib/
│ ├── generators/
│ │ ├── pundit/
│ │ │ ├── install/
│ │ │ │ ├── USAGE
│ │ │ │ ├── install_generator.rb
│ │ │ │ └── templates/
│ │ │ │ └── application_policy.rb.tt
│ │ │ └── policy/
│ │ │ ├── USAGE
│ │ │ ├── policy_generator.rb
│ │ │ └── templates/
│ │ │ └── policy.rb.tt
│ │ ├── rspec/
│ │ │ ├── policy_generator.rb
│ │ │ └── templates/
│ │ │ └── policy_spec.rb.tt
│ │ └── test_unit/
│ │ ├── policy_generator.rb
│ │ └── templates/
│ │ └── policy_test.rb.tt
│ ├── pundit/
│ │ ├── authorization.rb
│ │ ├── cache_store/
│ │ │ ├── legacy_store.rb
│ │ │ └── null_store.rb
│ │ ├── cache_store.rb
│ │ ├── context.rb
│ │ ├── error.rb
│ │ ├── helper.rb
│ │ ├── policy_finder.rb
│ │ ├── railtie.rb
│ │ ├── rspec.rb
│ │ └── version.rb
│ └── pundit.rb
├── pundit.gemspec
└── spec/
├── authorization_spec.rb
├── generators_spec.rb
├── policies/
│ └── post_policy_spec.rb
├── policy_finder_spec.rb
├── pundit/
│ └── helper_spec.rb
├── pundit_spec.rb
├── rspec_dsl_spec.rb
├── spec_helper.rb
└── support/
├── lib/
│ ├── controller.rb
│ ├── custom_cache.rb
│ └── instance_tracking.rb
├── models/
│ ├── article.rb
│ ├── article_tag.rb
│ ├── artificial_blog.rb
│ ├── blog.rb
│ ├── comment.rb
│ ├── comment_four_five_six.rb
│ ├── comment_scope.rb
│ ├── comments_relation.rb
│ ├── customer/
│ │ └── post.rb
│ ├── default_scope_contains_error.rb
│ ├── dummy_current_user.rb
│ ├── foo.rb
│ ├── post.rb
│ ├── post_four_five_six.rb
│ ├── project_one_two_three/
│ │ ├── avatar_four_five_six.rb
│ │ └── tag_four_five_six.rb
│ └── wiki.rb
└── policies/
├── article_tag_other_name_policy.rb
├── base_policy.rb
├── blog_policy.rb
├── comment_policy.rb
├── criteria_policy.rb
├── default_scope_contains_error_policy.rb
├── dummy_current_user_policy.rb
├── nil_class_policy.rb
├── post_policy.rb
├── project/
│ ├── admin/
│ │ └── comment_policy.rb
│ ├── comment_policy.rb
│ ├── criteria_policy.rb
│ └── post_policy.rb
├── project_one_two_three/
│ ├── avatar_four_five_six_policy.rb
│ ├── comment_four_five_six_policy.rb
│ ├── criteria_four_five_six_policy.rb
│ ├── post_four_five_six_policy.rb
│ └── tag_four_five_six_policy.rb
├── publication_policy.rb
└── wiki_policy.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a bug report to report a problem
title: ''
labels: problem
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps or runnable code to reproduce the problem.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea
title: ''
labels: ['feature request']
assignees: ''
---
**Please consider**
- Could this feature break backwards-compatibility?
- Could this feature benefit the many who use Pundit?
- Could this feature be useful in _most_ projects that use Pundit?
- Would this feature require Rails?
- Am I open to creating a Pull Request with the necessary changes?
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of how you'd like to approach solving the problem.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context. Ex. if you've solved this problem in your own projects already, how that worked, and why the feature should be moved and maintained in Pundit instead.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/gem_release_template.md
================================================
## To do
- [ ] Make changes:
- [ ] Bump `Pundit::VERSION` in `lib/pundit/version.rb`.
- [ ] Update `CHANGELOG.md`.
- [ ] Open pull request 🚀 and merge it.
- [ ] Run [push gem](https://github.com/varvet/pundit/actions/workflows/push_gem.yml) GitHub Action.
- [ ] Make an announcement in [Pundit discussions](https://github.com/varvet/pundit/discussions/categories/announcements)
================================================
FILE: .github/pull_request_template.md
================================================
## To do
- [ ] I have read the [contributing guidelines](https://github.com/varvet/pundit/contribute).
- [ ] I have added relevant tests.
- [ ] I have adjusted relevant documentation.
- [ ] I have made sure the individual commits are meaningful.
- [ ] I have added relevant lines to the CHANGELOG.
PS: Thank you for contributing to Pundit ❤️
================================================
FILE: .github/workflows/main.yml
================================================
name: Main
on:
push:
branches: ["main"]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
matrix-test:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.allow-failure || false }}
strategy:
fail-fast: false
matrix:
ruby-version:
- "3.2"
- "3.3"
- "3.4"
- "4.0"
- "jruby-9.4"
- "jruby"
include: # HEAD-versions
- ruby-version: "head"
allow-failure: true
- ruby-version: "jruby-head"
allow-failure: true
- ruby-version: "truffleruby-head"
allow-failure: true
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
rubygems: latest
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- name: Run tests
run: bundle exec rspec
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
rubygems: latest
ruby-version: "ruby"
bundler-cache: true
- name: Run tests
run: bundle exec rspec
env:
COVERAGE: 1
- name: Upload coverage results
uses: actions/upload-artifact@v4
with:
include-hidden-files: true
name: coverage-results
path: coverage
retention-days: 1
linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
rubygems: default
ruby-version: "ruby"
bundler-cache: false
- run: bundle install
- name: Run standard
run: bundle exec standardrb
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
rubygems: default
ruby-version: "ruby"
bundler-cache: false
- run: bundle install
- run: rake yard
required-checks:
runs-on: ubuntu-latest
if: ${{ always() }}
needs:
- test
- matrix-test
- docs
- linting
steps:
- name: failure
if: ${{ failure() || contains(needs.*.result, 'failure') }}
run: exit 1
- name: success
run: exit 0
================================================
FILE: .github/workflows/push_gem.yml
================================================
name: Push Gem
on:
workflow_dispatch:
permissions:
contents: read
jobs:
push:
if: github.repository == 'varvet/pundit'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
# Set up
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Ruby
uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0
with:
bundler-cache: true
ruby-version: ruby
# Release
- uses: rubygems/release-gem@612653d273a73bdae1df8453e090060bb4db5f31 # v1+ unreleased
================================================
FILE: .gitignore
================================================
*.gem
*.rbc
.bundle
.config
.coverage
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
================================================
FILE: .rubocop_ignore_git.yml
================================================
# This is here so we can keep YAML syntax highlight in the main file.
AllCops:
Exclude:
- "lib/generators/**/templates/**/*"
<% `git status --ignored --porcelain`.lines.grep(/^!! /).each do |path| %>
- <%= path.sub(/^!! /, '').sub(/\/$/, '/**/*') %>
<% end %>
================================================
FILE: .standard.yml
================================================
parallel: true
ruby_version: 3.2
extend_config:
- .rubocop_ignore_git.yml
================================================
FILE: .yardopts
================================================
--no-private --private --protected --hide-void-return --markup markdown --fail-on-warning
================================================
FILE: CHANGELOG.md
================================================
# Pundit
## Unreleased
- Add support for `params.expect` using `expected_parameters` and `expected_parameters_for`. [#855](https://github.com/varvet/pundit/pull/855)
### Fixed
- Update for rspec 4 breaking changes [#873](https://github.com/varvet/pundit/issues/873)
## 2.5.2 (2025-09-24)
### Fixed
- Added `config/rubocop-rspec.yml` back from accidentally being excluded [#866](https://github.com/varvet/pundit/issues/866)
## 2.5.1 (2025-09-12)
### Fixed
- Requiring only `pundit/rspec` no longer raises an error in Active Support [#857](https://github.com/varvet/pundit/issues/857)
## 2.5.0 (2025-03-03)
### Added
- Add `Pundit::Authorization#pundit_reset!` hook to reset the policy and policy scope cache. [#830](https://github.com/varvet/pundit/issues/830)
- Add links to gemspec. [#845](https://github.com/varvet/pundit/issues/845)
- Register policies directories for Rails 8 code statistics [#833](https://github.com/varvet/pundit/issues/833)
- Added an example for how to use pundit with Rails 8 authentication generator [#850](https://github.com/varvet/pundit/issues/850)
### Changed
- Deprecated `Pundit::SUFFIX`, moved it to `Pundit::PolicyFinder::SUFFIX` [#835](https://github.com/varvet/pundit/issues/835)
- Explicitly require less of `active_support` [#837](https://github.com/varvet/pundit/issues/837)
- Using `permit` matcher without a surrouding `permissions` block now raises a useful error. [#836](https://github.com/varvet/pundit/issues/836)
### Fixed
- Using a hash as custom cache in `Pundit.authorize` now works as documented. [#838](https://github.com/varvet/pundit/issues/838)
## 2.4.0 (2024-08-26)
### Changed
- Improve the `NotAuthorizedError` message to include the policy class.
Furthermore, in the case where the record passed is a class instead of an instance, the class name is given. [#812](https://github.com/varvet/pundit/issues/812)
### Added
- Add customizable permit matcher description [#806](https://github.com/varvet/pundit/issues/806)
- Add support for filter_run_when_matching :focus with permissions helper. [#820](https://github.com/varvet/pundit/issues/820)
## 2.3.2 (2024-05-08)
- Refactor: First pass of Pundit::Context [#797](https://github.com/varvet/pundit/issues/797)
### Changed
- Update `ApplicationPolicy` generator to qualify the `Scope` class name [#792](https://github.com/varvet/pundit/issues/792)
- Policy generator uses `NoMethodError` to indicate `#resolve` is not implemented [#776](https://github.com/varvet/pundit/issues/776)
## Deprecated
- Dropped support for Ruby 3.0 [#796](https://github.com/varvet/pundit/issues/796)
## 2.3.1 (2023-07-17)
### Fixed
- Use `Kernel.warn` instead of `ActiveSupport::Deprecation.warn` for deprecations [#764](https://github.com/varvet/pundit/issues/764)
- Policy generator now works on Ruby 3.2 [#754](https://github.com/varvet/pundit/issues/754)
## 2.3.0 (2022-12-19)
### Added
- add support for rubocop-rspec syntax extensions [#745](https://github.com/varvet/pundit/issues/745)
## 2.2.0 (2022-02-11)
### Fixed
- Using `policy_class` and a namespaced record now passes only the record when instantiating the policy. (#697, #689, #694, #666)
### Changed
- Require users to explicitly define Scope#resolve in generated policies (#711, #722)
### Deprecated
- Deprecate `include Pundit` in favor of `include Pundit::Authorization` [#621](https://github.com/varvet/pundit/issues/621)
## 2.1.1 (2021-08-13)
Friday 13th-release!
Careful! The bugfix below [#626](https://github.com/varvet/pundit/issues/626) could break existing code. If you rely on the
return value for `authorize` and namespaced policies you might need to do some
changes.
### Fixed
- `.authorize` and `#authorize` return the instance, even for namespaced
policies [#626](https://github.com/varvet/pundit/issues/626)
### Changed
- Generate application scope with `protected` attr_readers. [#616](https://github.com/varvet/pundit/issues/616)
### Removed
- Dropped support for Ruby end-of-life versions: 2.1 and 2.2. [#604](https://github.com/varvet/pundit/issues/604)
- Dropped support for Ruby end-of-life versions: 2.3 [#633](https://github.com/varvet/pundit/issues/633)
- Dropped support for Ruby end-of-life versions: 2.4, 2.5 and JRuby 9.1 [#676](https://github.com/varvet/pundit/issues/676)
- Dropped support for RSpec 2 [#615](https://github.com/varvet/pundit/issues/615)
## 2.1.0 (2019-08-14)
### Fixed
- Avoid name clashes with the Error class. [#590](https://github.com/varvet/pundit/issues/590)
### Changed
- Return a safer default NotAuthorizedError message. [#583](https://github.com/varvet/pundit/issues/583)
## 2.0.1 (2019-01-18)
### Breaking changes
None
### Other changes
- Improve exception handling for `#policy_scope` and `#policy_scope!`. [#550](https://github.com/varvet/pundit/issues/550)
- Add `:policy` metadata to RSpec template. [#566](https://github.com/varvet/pundit/issues/566)
## 2.0.0 (2018-07-21)
No changes since beta1
## 2.0.0.beta1 (2018-07-04)
### Breaking changes
- Only pass last element of "namespace array" to policy and scope. [#529](https://github.com/varvet/pundit/issues/529)
- Raise `InvalidConstructorError` if a policy or policy scope with an invalid constructor is called. [#462](https://github.com/varvet/pundit/issues/462)
- Return passed object from `#authorize` method to make chaining possible. [#385](https://github.com/varvet/pundit/issues/385)
### Other changes
- Add `policy_class` option to `authorize` to be able to override the policy. [#441](https://github.com/varvet/pundit/issues/441)
- Add `policy_scope_class` option to `authorize` to be able to override the policy scope. [#441](https://github.com/varvet/pundit/issues/441)
- Fix `param_key` issue when passed an array. [#529](https://github.com/varvet/pundit/issues/529)
- Allow specification of a `NilClassPolicy`. [#525](https://github.com/varvet/pundit/issues/525)
- Make sure `policy_class` override is called when passed an array. [#475](https://github.com/varvet/pundit/issues/475)
- Use `action_name` instead of `params[:action]`. [#419](https://github.com/varvet/pundit/issues/419)
- Add `pundit_params_for` method to make it easy to customize params fetching. [#502](https://github.com/varvet/pundit/issues/502)
## 1.1.0 (2016-01-14)
- Can retrieve policies via an array of symbols/objects.
- Add autodetection of param key to `permitted_attributes` helper.
- Hide some methods which should not be actions.
- Permitted attributes should be expanded.
- Generator uses `RSpec.describe` according to modern best practices.
## 1.0.1 (2015-05-27)
- Fixed a regression where NotAuthorizedError could not be ininitialized with a string.
- Use `camelize` instead of `classify` for symbol policies to prevent weird pluralizations.
## 1.0.0 (2015-04-19)
- Caches policy scopes and policies.
- Explicitly setting the policy for the controller via `controller.policy = foo` has been removed. Instead use `controller.policies[record] = foo`.
- Explicitly setting the policy scope for the controller via `controller.policy_policy = foo` has been removed. Instead use `controller.policy_scopes[scope] = foo`.
- Add `permitted_attributes` helper to fetch attributes from policy.
- Add `pundit_policy_authorized?` and `pundit_policy_scoped?` methods.
- Instance variables are prefixed to avoid collisions.
- Add `Pundit.authorize` method.
- Add `skip_authorization` and `skip_policy_scope` helpers.
- Better errors when checking multiple permissions in RSpec tests.
- Better errors in case `nil` is passed to `policy` or `policy_scope`.
- Use `inspect` when printing object for better errors.
- Dropped official support for Ruby 1.9.3
## 0.3.0 (2014-08-22)
- Extend the default `ApplicationPolicy` with an `ApplicationPolicy::Scope` [#120](https://github.com/varvet/pundit/issues/120)
- Fix RSpec 3 deprecation warnings for built-in matchers [#162](https://github.com/varvet/pundit/issues/162)
- Generate blank policy spec/test files for Rspec/MiniTest/Test::Unit in Rails [#138](https://github.com/varvet/pundit/issues/138)
## 0.2.3 (2014-04-06)
- Customizable error messages: `#query`, `#record` and `#policy` methods on `Pundit::NotAuthorizedError` [#114](https://github.com/varvet/pundit/issues/114)
- Raise a different `Pundit::AuthorizationNotPerformedError` when `authorize` call is expected in controller action but missing [#109](https://github.com/varvet/pundit/issues/109)
- Update Rspec matchers for Rspec 3 [#124](https://github.com/varvet/pundit/issues/124)
## 0.2.2 (2014-02-07)
- Customize the user to be passed into policies: `pundit_user` [#42](https://github.com/varvet/pundit/issues/42)
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all
people who contribute through reporting issues, posting feature requests,
updating documentation, submitting pull requests or patches, and other
activities.
We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual
language or imagery, derogatory comments or personal attacks, trolling, public
or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct. Project maintainers who do not
follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by opening an issue or contacting one or more of the project
maintainers.
This Code of Conduct is adapted from the [Contributor
Covenant](http:contributor-covenant.org), version 1.0.0, available at
[https://contributor-covenant.org/version/1/0/0/](https://contributor-covenant.org/version/1/0/0/)
================================================
FILE: CONTRIBUTING.md
================================================
## Security issues
If you have found a security related issue, please do not file an issue on GitHub or send a PR addressing the issue. Refer to [SECURITY.md](./SECURITY.md) for instructions.
## Reporting issues
Please try to answer the following questions in your bug report:
- What did you do?
- What did you expect to happen?
- What happened instead?
Make sure to include as much relevant information as possible. Ruby version,
Pundit version, OS version and any stack traces you have are very valuable.
## Pull Requests
- **Add tests!** Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour**. Make sure the README and any other
relevant documentation are kept up-to-date.
- **Create topic branches**. Please don't ask us to pull from your main branch.
- **One pull request per feature**. If you want to do more than one thing, send
multiple pull requests.
- **Send coherent history**. Make sure each individual commit in your pull
request is meaningful. If you had to make multiple intermediate commits while
developing, please squash them before sending them to us.
- **Update the CHANGELOG.** Don't forget to add your new changes to the CHANGELOG.
================================================
FILE: Gemfile
================================================
# frozen_string_literal: true
source "https://rubygems.org"
gemspec
# Rails-related - for testing purposes
gem "actionpack", ">= 3.0.0" # Used to test strong parameters
gem "activemodel", ">= 3.0.0" # Used to test ActiveModel::Naming
gem "railties", ">= 3.0.0" # Used to test generators
# Testing
gem "rspec", ">= 3.0.0"
gem "simplecov", ">= 0.17.0"
# Development tools
gem "bundler"
gem "rake"
gem "standard"
gem "yard"
gem "zeitwerk"
# Affects us on JRuby 9.3.15.
#
# @see https://github.com/rails/rails/issues/54260
gem "logger"
================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2019 Jonas Nicklas, Varvet AB
MIT License
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.
================================================
FILE: README.md
================================================
# Pundit
[](https://github.com/varvet/pundit/actions/workflows/main.yml)
[](https://inch-ci.org/github/varvet/pundit)
[](https://badge.fury.io/rb/pundit)
Pundit provides a set of helpers which guide you in leveraging regular Ruby
classes and object oriented design patterns to build a straightforward, robust, and
scalable authorization system.
## Links:
- [API documentation for the most recent version](https://www.rubydoc.info/gems/pundit)
- [Source Code](https://github.com/varvet/pundit)
- [Contributing](https://github.com/varvet/pundit/blob/main/CONTRIBUTING.md)
- [Code of Conduct](https://github.com/varvet/pundit/blob/main/CODE_OF_CONDUCT.md)
<strong>Sponsored by:</strong> <a href="https://www.varvet.com">Varvet<br><br><img src="https://github.com/varvet/pundit/assets/99166/aa9efa0a-6903-4037-abee-1824edc57f1a" alt="Varvet logo" height="120"></div>
## Installation
> **Please note** that the README on GitHub is accurate with the _latest code on GitHub_. You are most likely using a released version of Pundit, so please refer to the [documentation for the latest released version of Pundit](https://www.rubydoc.info/gems/pundit).
``` sh
bundle add pundit
```
Include `Pundit::Authorization` in your application controller:
``` ruby
class ApplicationController < ActionController::Base
include Pundit::Authorization
end
```
Optionally, you can run the generator, which will set up an application policy
with some useful defaults for you:
``` sh
rails g pundit:install
```
After generating your application policy, restart the Rails server so that Rails
can pick up any classes in the new `app/policies/` directory.
## Policies
Pundit is focused around the notion of policy classes. We suggest that you put
these classes in `app/policies`. This is an example that allows updating a post
if the user is an admin, or if the post is unpublished:
``` ruby
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
@user = user
@post = post
end
def update?
user.admin? || !post.published?
end
end
```
As you can see, this is a plain Ruby class. Pundit makes the following
assumptions about this class:
- The class has the same name as some kind of model class, only suffixed
with the word "Policy".
- The first argument is a user. In your controller, Pundit will call the
`current_user` method to retrieve what to send into this argument
- The second argument is some kind of model object, whose authorization
you want to check. This does not need to be an ActiveRecord or even
an ActiveModel object, it can be anything really.
- The class implements some kind of query method, in this case `update?`.
Usually, this will map to the name of a particular controller action.
That's it really.
Usually you'll want to inherit from the application policy created by the
generator, or set up your own base class to inherit from:
``` ruby
class PostPolicy < ApplicationPolicy
def update?
user.admin? or not record.published?
end
end
```
In the generated `ApplicationPolicy`, the model object is called `record`.
Supposing that you have an instance of class `Post`, Pundit now lets you do
this in your controller:
``` ruby
def update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
```
The authorize method automatically infers that `Post` will have a matching
`PostPolicy` class, and instantiates this class, handing in the current user
and the given record. It then infers from the action name, that it should call
`update?` on this instance of the policy. In this case, you can imagine that
`authorize` would have done something like this:
``` ruby
unless PostPolicy.new(current_user, @post).update?
raise Pundit::NotAuthorizedError, "not allowed to PostPolicy#update? this Post"
end
```
You can pass a second argument to `authorize` if the name of the permission you
want to check doesn't match the action name. For example:
``` ruby
def publish
@post = Post.find(params[:id])
authorize @post, :update?
@post.publish!
redirect_to @post
end
```
You can pass an argument to override the policy class if necessary. For example:
```ruby
def create
@publication = find_publication # assume this method returns any model that behaves like a publication
# @publication.class => Post
authorize @publication, policy_class: PublicationPolicy
@publication.publish!
redirect_to @publication
end
```
If you don't have an instance for the first argument to `authorize`, then you can pass
the class. For example:
Policy:
```ruby
class PostPolicy < ApplicationPolicy
def admin_list?
user.admin?
end
end
```
Controller:
```ruby
def admin_list
authorize Post # we don't have a particular post to authorize
# Rest of controller action
end
```
`authorize` returns the instance passed to it, so you can chain it like this:
Controller:
```ruby
def show
@user = authorize User.find(params[:id])
end
```
You can easily get a hold of an instance of the policy through the `policy`
method in both the view and controller. This is especially useful for
conditionally showing links or buttons in the view:
``` erb
<% if policy(@post).update? %>
<%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
```
## Headless policies
Given there is a policy without a corresponding model / ruby class,
you can retrieve it by passing a symbol.
```ruby
# app/policies/dashboard_policy.rb
class DashboardPolicy
attr_reader :user
# `_record` in this example will be :dashboard
def initialize(user, _record)
@user = user
end
def show?
user.admin?
end
end
```
Note that the headless policy still needs to accept two arguments. The
second argument will be the symbol `:dashboard` in this case, which
is what is passed as the record to `authorize` below.
```ruby
# In controllers
def show
authorize :dashboard, :show?
...
end
```
```erb
# In views
<% if policy(:dashboard).show? %>
<%= link_to 'Dashboard', dashboard_path %>
<% end %>
```
## Scopes
Often, you will want to have some kind of view listing records which a
particular user has access to. When using Pundit, you are expected to
define a class called a policy scope. It can look something like this:
``` ruby
class PostPolicy < ApplicationPolicy
class Scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(published: true)
end
end
private
attr_reader :user, :scope
end
def update?
user.admin? or not record.published?
end
end
```
Pundit makes the following assumptions about this class:
- The class has the name `Scope` and is nested under the policy class.
- The first argument is a user. In your controller, Pundit will call the
`current_user` method to retrieve what to send into this argument.
- The second argument is a scope of some kind on which to perform some kind of
query. It will usually be an ActiveRecord class or a
`ActiveRecord::Relation`, but it could be something else entirely.
- Instances of this class respond to the method `resolve`, which should return
some kind of result which can be iterated over. For ActiveRecord classes,
this would usually be an `ActiveRecord::Relation`.
You'll probably want to inherit from the application policy scope generated by the
generator, or create your own base class to inherit from:
``` ruby
class PostPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
if user.admin?
scope.all
else
scope.where(published: true)
end
end
end
def update?
user.admin? or not record.published?
end
end
```
You can now use this class from your controller via the `policy_scope` method:
``` ruby
def index
@posts = policy_scope(Post)
end
def show
@post = policy_scope(Post).find(params[:id])
end
```
Like with the authorize method, you can also override the policy scope class:
``` ruby
def index
# publication_class => Post
@publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope)
end
```
In this case it is a shortcut for doing:
``` ruby
def index
@publications = PublicationPolicy::Scope.new(current_user, Post).resolve
end
```
You can, and are encouraged to, use this method in views:
``` erb
<% policy_scope(@user.posts).each do |post| %>
<p><%= link_to post.title, post_path(post) %></p>
<% end %>
```
## Ensuring policies and scopes are used
When you are developing an application with Pundit it can be easy to forget to
authorize some action. People are forgetful after all. Since Pundit encourages
you to add the `authorize` call manually to each controller action, it's really
easy to miss one.
Thankfully, Pundit has a handy feature which reminds you in case you forget.
Pundit tracks whether you have called `authorize` anywhere in your controller
action. Pundit also adds a method to your controllers called
`verify_authorized`. This method will raise an exception if `authorize` has not
yet been called. You should run this method in an `after_action` hook to ensure
that you haven't forgotten to authorize the action. For example:
``` ruby
class ApplicationController < ActionController::Base
include Pundit::Authorization
after_action :verify_authorized
end
```
Likewise, Pundit also adds `verify_policy_scoped` to your controller. This
will raise an exception similar to `verify_authorized`. However, it tracks
if `policy_scope` is used instead of `authorize`. This is mostly useful for
controller actions like `index` which find collections with a scope and don't
authorize individual instances.
``` ruby
class ApplicationController < ActionController::Base
include Pundit::Authorization
after_action :verify_pundit_authorization
def verify_pundit_authorization
if action_name == "index"
verify_policy_scoped
else
verify_authorized
end
end
end
```
**This verification mechanism only exists to aid you while developing your
application, so you don't forget to call `authorize`. It is not some kind of
failsafe mechanism or authorization mechanism. You should be able to remove
these filters without affecting how your app works in any way.**
Some people have found this feature confusing, while many others
find it extremely helpful. If you fall into the category of people who find it
confusing then you do not need to use it. Pundit will work fine without
using `verify_authorized` and `verify_policy_scoped`.
### Conditional verification
If you're using `verify_authorized` in your controllers but need to
conditionally bypass verification, you can use `skip_authorization`. For
bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful
in circumstances where you don't want to disable verification for the
entire action, but have some cases where you intend to not authorize.
```ruby
def show
record = Record.find_by(attribute: "value")
if record.present?
authorize record
else
skip_authorization
end
end
```
## Manually specifying policy classes
Sometimes you might want to explicitly declare which policy to use for a given
class, instead of letting Pundit infer it. This can be done like so:
``` ruby
class Post
def self.policy_class
PostablePolicy
end
end
```
Alternatively, you can declare an instance method:
``` ruby
class Post
def policy_class
PostablePolicy
end
end
```
## Plain old Ruby
Pundit is a very small library on purpose, and it doesn't do anything you can't do yourself. There's no secret sauce here. It does as little as possible, and then gets out of your way.
With the few but powerful helpers available in Pundit, you have the power to build a well structured, fully working authorization system without using any special DSLs or funky syntax.
Remember that all of the policy and scope classes are plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Use `alias_method` to make some permissions behave the same as others. Inherit from a base set of permissions. Use metaprogramming if you really have to.
## Generator
Use the supplied generator to generate policies:
``` sh
rails g pundit:policy post
```
## Closed systems
In many applications, only logged in users are really able to do anything. If
you're building such a system, it can be kind of cumbersome to check that the
user in a policy isn't `nil` for every single permission. Aside from policies,
you can add this check to the base class for scopes.
We suggest that you define a filter that redirects unauthenticated users to the
login page. As a secondary defence, if you've defined an ApplicationPolicy, it
might be a good idea to raise an exception if somehow an unauthenticated user
got through. This way you can fail more gracefully.
``` ruby
class ApplicationPolicy
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
@user = user
@record = record
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
@user = user
@scope = scope
end
end
end
```
## NilClassPolicy
To support a [null object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern)
you may find that you want to implement a `NilClassPolicy`. This might be useful
where you want to extend your ApplicationPolicy to allow some tolerance of, for
example, associations which might be `nil`.
```ruby
class NilClassPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
raise Pundit::NotDefinedError, "Cannot scope NilClass"
end
end
def show?
false # Nobody can see nothing
end
end
```
## Rescuing a denied Authorization in Rails
Pundit raises a `Pundit::NotAuthorizedError` you can
[rescue_from](https://guides.rubyonrails.org/action_controller_overview.html#rescue-from)
in your `ApplicationController`. You can customize the `user_not_authorized`
method in every controller.
```ruby
class ApplicationController < ActionController::Base
include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_back_or_to(root_path)
end
end
```
Alternatively, you can globally handle Pundit::NotAuthorizedError's by having rails handle them as a 403 error and serving a 403 error page. Add the following to application.rb:
```config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden```
## Creating custom error messages
`NotAuthorizedError`s provide information on what query (e.g. `:create?`), what
record (e.g. an instance of `Post`), and what policy (e.g. an instance of
`PostPolicy`) caused the error to be raised.
One way to use these `query`, `record`, and `policy` properties is to connect
them with `I18n` to generate error messages. Here's how you might go about doing
that.
```ruby
class ApplicationController < ActionController::Base
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized(exception)
policy_name = exception.policy.class.to_s.underscore
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
redirect_back_or_to(root_path)
end
end
```
```yaml
en:
pundit:
default: 'You cannot perform this action.'
post_policy:
update?: 'You cannot edit this post!'
create?: 'You cannot create posts!'
```
This is an example. Pundit is agnostic as to how you implement your error messaging.
## Manually retrieving policies and scopes
Sometimes you want to retrieve a policy for a record outside the controller or
view. For example when you delegate permissions from one policy to another.
You can easily retrieve policies and scopes like this:
``` ruby
Pundit.policy!(user, post)
Pundit.policy(user, post)
Pundit.policy_scope!(user, Post)
Pundit.policy_scope(user, Post)
```
The bang methods will raise an exception if the policy does not exist, whereas
those without the bang will return nil.
## Customize Pundit user
On occasion, your controller may be unable to access `current_user`, or the method that should be invoked by Pundit may not be `current_user`. To address this, you can define a method in your controller named `pundit_user`.
```ruby
def pundit_user
User.find_by_other_means
end
```
For instance, Rails 8 includes a built-in [authentication generator](https://github.com/rails/rails/tree/8-0-stable/railties/lib/rails/generators/rails/authentication). If you choose to use it, the currently logged-in user is accessed via `Current.user` instead of `current_user`.
To ensure compatibility with Pundit, define a `pundit_user` method in `application_controller.rb` (or another suitable location) as follows:
```ruby
def pundit_user
Current.user
end
```
### Handling User Switching in Pundit
When switching users in your application, it's important to reset the Pundit user context to ensure that authorization policies are applied correctly for the new user. Pundit caches the user context, so failing to reset it could result in incorrect permissions being applied.
To handle user switching, you can use the following pattern in your controller:
```ruby
class ApplicationController
include Pundit::Authorization
def switch_user_to(user)
terminate_session if authenticated?
start_new_session_for user
pundit_reset!
end
end
```
Make sure to invoke `pundit_reset!` whenever changing the user. This ensures the cached authorization context is reset, preventing any incorrect permissions from being applied.
## Policy Namespacing
In some cases it might be helpful to have multiple policies that serve different contexts for a resource. A prime example of this is the case where User policies differ from Admin policies. To authorize with a namespaced policy, pass the namespace into the `authorize` helper in an array:
```ruby
authorize(post) # => will look for a PostPolicy
authorize([:admin, post]) # => will look for an Admin::PostPolicy
authorize([:foo, :bar, post]) # => will look for a Foo::Bar::PostPolicy
policy_scope(Post) # => will look for a PostPolicy::Scope
policy_scope([:admin, Post]) # => will look for an Admin::PostPolicy::Scope
policy_scope([:foo, :bar, Post]) # => will look for a Foo::Bar::PostPolicy::Scope
```
If you are using namespaced policies for something like Admin areas, we recommend defining a `pundit_namespace` hook in your `ApplicationController` and overriding it in namespaced controllers:
```ruby
class ApplicationController < ActionController::Base
include Pundit::Authorization
private
def pundit_namespace(record) = record
def authorize(record, ...) = super(pundit_namespace(record), ...)
def policy_scope(scope, ...) = super(pundit_namespace(scope), ...)
end
class AdminController < ApplicationController
private
# Override the pundit namespace in Admin.
def pundit_namespace(record) = [:admin, record]
end
class Admin::PostController < AdminController
def index
policy_scope(Post)
end
def show
post = authorize Post.find(params[:id])
end
end
```
## Additional context
Pundit strongly encourages you to model your application in such a way that the
only context you need for authorization is a user object and a domain model that
you want to check authorization for. If you find yourself needing more context than
that, consider whether you are authorizing the right domain model, maybe another
domain model (or a wrapper around multiple domain models) can provide the context
you need.
Pundit does not allow you to pass additional arguments to policies for precisely
this reason.
However, in very rare cases, you might need to authorize based on more context than just
the currently authenticated user. Suppose for example that authorization is dependent
on IP address in addition to the authenticated user. In that case, one option is to
create a special class which wraps up both user and IP and passes it to the policy.
``` ruby
class UserContext < Data.define(:user, :ip)
end
class ApplicationController
include Pundit::Authorization
def pundit_user = UserContext.new(current_user, request.ip)
end
```
## Strong parameters
In Rails, [mass-assignment protection is handled in the controller](https://guides.rubyonrails.org/action_controller_overview.html#strong-parameters). With Pundit you can control which attributes a user has access to update via your policies. You can set up an `expected_attributes_for_action(action_name)` method in your policy like this:
```ruby
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def expected_attributes_for_action(_action_name)
if user.admin? || user.owner_of?(post)
[:title, :body, :tag_list]
else
[:tag_list]
end
end
end
```
You can now retrieve these attributes from the policy:
```ruby
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
private
def post_params
params.expect(policy(@post).expected_attributes_for_action(action_name))
end
end
```
However, this is a bit cumbersome, so Pundit provides a convenient helper method with `#expected_attributes`:
```ruby
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
if @post.update(expected_attributes(@post))
redirect_to @post
else
render :edit
end
end
end
```
### Permitted Parameters
Pundit still support the old `params.require.permit()` style of permitting attributes, although `params.expect()` is preferred.
If you need to fetch parameters based on namespaces different from the suggested one, override the below method, in your controller, and return an instance of `ActionController::Parameters`.
```ruby
def pundit_params_for(record)
params.require(pundit_param_key(record))
end
```
For example:
```ruby
# If you don't want to use require
def pundit_params_for(record)
params.fetch(pundit_param_key(record), {})
end
# If you are using something like the JSON API spec
def pundit_params_for(_record)
params.fetch(:data, {}).fetch(:attributes, {})
end
```
## RSpec
### Policy Specs
> [!TIP]
> An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
[excellent post](https://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/) and implemented in the third party [pundit-matchers](https://github.com/punditcommunity/pundit-matchers) gem.
Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec.
Require `pundit/rspec` in your `spec_helper.rb`:
``` ruby
require "pundit/rspec"
```
Then put your policy specs in `spec/policies`, and make them look somewhat like this:
``` ruby
describe PostPolicy do
subject { described_class }
permissions :update?, :edit? do
it "denies access if post is published" do
expect(subject).not_to permit(User.new(admin: false), Post.new(published: true))
end
it "grants access if post is published and user is an admin" do
expect(subject).to permit(User.new(admin: true), Post.new(published: true))
end
it "grants access if post is unpublished" do
expect(subject).to permit(User.new(admin: false), Post.new(published: false))
end
end
end
```
### Custom matcher description
By default rspec includes an inspected `user` and `record` in the matcher description, which might become overly verbose:
```
PostPolicy
update? and show?
is expected to permit #<User:0x0000000104aefd80> and #<Post:0x0000000104aef8d0 @user=#<User:0x0000000104aefd80>>
```
You can override the default description with a static string, or a block:
```ruby
# static alternative: Pundit::RSpec::Matchers.description = "permit the user"
Pundit::RSpec::Matchers.description = ->(user, record) do
"permit user with role #{user.role} to access record with ID #{record.id}"
end
```
Which would make for a less chatty output:
```
PostPolicy
update? and show?
is expected to permit user with role admin to access record with ID 130
```
### Focus Support
If your RSpec config has `filter_run_when_matching :focus`, you may tag the `permissions` helper like so:
```
permissions :show?, :focus do
```
### Scope Specs
Pundit does not provide a DSL for testing scopes. Test them like you would a regular Ruby class!
### Linting with RuboCop RSpec
When you lint your RSpec spec files with `rubocop-rspec`, it will fail to properly detect RSpec constructs that Pundit defines, `permissions`.
Make sure to use `rubocop-rspec` 2.0 or newer and add the following to your `.rubocop.yml`:
```yaml
inherit_gem:
pundit: config/rubocop-rspec.yml
```
# External Resources
- [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
- [Migrating to Pundit from CanCan](https://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
- [Testing Pundit Policies with RSpec](https://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
- [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
- [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
- [Straightforward Rails Authorization with Pundit](https://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
## Other implementations
- [Flask-Pundit](https://github.com/anurag90x/flask-pundit) (Python) is a [Flask](https://flask.pocoo.org/) extension "heavily inspired by" Pundit
# License
Licensed under the MIT license, see the separate LICENSE.txt file.
================================================
FILE: Rakefile
================================================
# frozen_string_literal: true
require "rubygems"
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "yard"
require "rubocop/rake_task"
RuboCop::RakeTask.new
desc "Run all examples"
RSpec::Core::RakeTask.new(:spec)
YARD::Rake::YardocTask.new do |t|
t.files = ["lib/**/*.rb"]
t.stats_options = ["--list-undoc"]
end
task default: :spec
================================================
FILE: SECURITY.md
================================================
# Security Policy
Please do not file an issue on GitHub, or send a PR addressing the issue.
## Supported versions
Most recent major version only.
## Reporting a vulnerability
Contact one of the maintainers directly:
* [@Burgestrand](https://github.com/Burgestrand)
* [@dgmstuart](https://github.com/dgmstuart)
* [@varvet](https://github.com/varvet)
You can report vulnerabilities on GitHub too: https://github.com/varvet/pundit/security
Thank you!
================================================
FILE: bin/console
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
require "pundit"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
require "irb"
IRB.start(__FILE__)
================================================
FILE: bin/setup
================================================
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
================================================
FILE: config/rubocop-rspec.yml
================================================
RSpec:
Language:
ExampleGroups:
Regular:
- permissions
================================================
FILE: lib/generators/pundit/install/USAGE
================================================
Description:
Generates an application policy as a starting point for your application.
================================================
FILE: lib/generators/pundit/install/install_generator.rb
================================================
# frozen_string_literal: true
module Pundit
# @private
module Generators
# @private
class InstallGenerator < ::Rails::Generators::Base
source_root File.expand_path("templates", __dir__)
def copy_application_policy
template "application_policy.rb.tt", "app/policies/application_policy.rb"
end
end
end
end
================================================
FILE: lib/generators/pundit/install/templates/application_policy.rb.tt
================================================
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
raise NoMethodError, "You must define #resolve in #{self.class}"
end
private
attr_reader :user, :scope
end
end
================================================
FILE: lib/generators/pundit/policy/USAGE
================================================
Description:
Generates a policy for a model with the given name.
Example:
rails generate pundit:policy user
This will create:
app/policies/user_policy.rb
================================================
FILE: lib/generators/pundit/policy/policy_generator.rb
================================================
# frozen_string_literal: true
module Pundit
# @private
module Generators
# @private
class PolicyGenerator < ::Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
def create_policy
template "policy.rb.tt", File.join("app/policies", class_path, "#{file_name}_policy.rb")
end
hook_for :test_framework
end
end
end
================================================
FILE: lib/generators/pundit/policy/templates/policy.rb.tt
================================================
<% module_namespacing do -%>
class <%= class_name %>Policy < ApplicationPolicy
# NOTE: Up to Pundit v2.3.1, the inheritance was declared as
# `Scope < Scope` rather than `Scope < ApplicationPolicy::Scope`.
# In most cases the behavior will be identical, but if updating existing
# code, beware of possible changes to the ancestors:
# https://gist.github.com/Burgestrand/4b4bc22f31c8a95c425fc0e30d7ef1f5
class Scope < ApplicationPolicy::Scope
# NOTE: Be explicit about which records you allow access to!
# def resolve
# scope.all
# end
end
end
<% end -%>
================================================
FILE: lib/generators/rspec/policy_generator.rb
================================================
# frozen_string_literal: true
# @private
module Rspec
# @private
module Generators
# @private
class PolicyGenerator < ::Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
def create_policy_spec
template "policy_spec.rb.tt", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
end
end
end
end
================================================
FILE: lib/generators/rspec/templates/policy_spec.rb.tt
================================================
require '<%= File.exist?('spec/rails_helper.rb') ? 'rails_helper' : 'spec_helper' %>'
RSpec.describe <%= class_name %>Policy, type: :policy do
let(:user) { User.new }
subject { described_class }
permissions ".scope" do
pending "add some examples to (or delete) #{__FILE__}"
end
permissions :show? do
pending "add some examples to (or delete) #{__FILE__}"
end
permissions :create? do
pending "add some examples to (or delete) #{__FILE__}"
end
permissions :update? do
pending "add some examples to (or delete) #{__FILE__}"
end
permissions :destroy? do
pending "add some examples to (or delete) #{__FILE__}"
end
end
================================================
FILE: lib/generators/test_unit/policy_generator.rb
================================================
# frozen_string_literal: true
# @private
module TestUnit
# @private
module Generators
# @private
class PolicyGenerator < ::Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
def create_policy_test
template "policy_test.rb.tt", File.join("test/policies", class_path, "#{file_name}_policy_test.rb")
end
end
end
end
================================================
FILE: lib/generators/test_unit/templates/policy_test.rb.tt
================================================
require 'test_helper'
class <%= class_name %>PolicyTest < ActiveSupport::TestCase
def test_scope
end
def test_show
end
def test_create
end
def test_update
end
def test_destroy
end
end
================================================
FILE: lib/pundit/authorization.rb
================================================
# frozen_string_literal: true
module Pundit
# Pundit DSL to include in your controllers to provide authorization helpers.
#
# @example
# class ApplicationController < ActionController::Base
# include Pundit::Authorization
# end
# @see #pundit
# @api public
# @since v2.2.0
module Authorization
extend ActiveSupport::Concern
included do
helper Helper if respond_to?(:helper)
if respond_to?(:helper_method)
helper_method :policy
helper_method :pundit_policy_scope
helper_method :pundit_user
end
end
protected
# An instance of {Pundit::Context} initialized with the current user.
#
# @note this method is memoized and will return the same instance during the request.
# @api public
# @return [Pundit::Context]
# @see #pundit_user
# @see #policies
# @since v2.3.2
def pundit
@pundit ||= Pundit::Context.new(
user: pundit_user,
policy_cache: Pundit::CacheStore::LegacyStore.new(policies)
)
end
# Hook method which allows customizing which user is passed to policies and
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
#
# @note Make sure to call `pundit_reset!` if this changes during a request.
# @see https://github.com/varvet/pundit#customize-pundit-user
# @see #pundit
# @see #pundit_reset!
# @return [Object] the user object to be used with pundit
# @since v0.2.2
def pundit_user
current_user
end
# Clears the cached Pundit authorization data.
#
# This method should be called when the pundit_user is changed,
# such as during user switching, to ensure that stale authorization
# data is not used. Pundit caches authorization policies and scopes
# for the pundit_user, so calling this method will reset those
# caches and ensure that the next authorization checks are performed
# with the correct context for the new pundit_user.
#
# @return [void]
# @since v2.5.0
def pundit_reset!
@pundit = nil
@_pundit_policies = nil
@_pundit_policy_scopes = nil
@_pundit_policy_authorized = nil
@_pundit_policy_scoped = nil
end
# @!group Policies
# Retrieves the policy for the given record, initializing it with the record
# and current user and finally throwing an error if the user is not
# authorized to perform the given action.
#
# @param record [Object, Array] the object we're checking permissions of
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
# If omitted then this defaults to the Rails controller action name.
# @param policy_class [Class] the policy class we want to force use of
# @raise [NotAuthorizedError] if the given query method returned false
# @return [record] Always returns the passed object record
# @see Pundit::Context#authorize
# @see #verify_authorized
# @since v0.1.0
def authorize(record, query = nil, policy_class: nil)
query ||= "#{action_name}?"
@_pundit_policy_authorized = true
pundit.authorize(record, query: query, policy_class: policy_class)
end
# Allow this action not to perform authorization.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @return [void]
# @see #verify_authorized
# @since v1.0.0
def skip_authorization
@_pundit_policy_authorized = :skipped
end
# @return [Boolean] wether or not authorization has been performed
# @see #authorize
# @see #skip_authorization
# @since v1.0.0
def pundit_policy_authorized?
!!@_pundit_policy_authorized
end
# Raises an error if authorization has not been performed.
#
# Usually used as an `after_action` filter to prevent programmer error in
# forgetting to call {#authorize} or {#skip_authorization}.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @raise [AuthorizationNotPerformedError] if authorization has not been performed
# @return [void]
# @see #authorize
# @see #skip_authorization
# @since v0.1.0
def verify_authorized
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
end
# rubocop:disable Naming/MemoizedInstanceVariableName
# Cache of policies. You should not rely on this method.
#
# @api private
# @since v1.0.0
def policies
@_pundit_policies ||= {}
end
# rubocop:enable Naming/MemoizedInstanceVariableName
# @!endgroup
# Retrieves the policy for the given record.
#
# @see https://github.com/varvet/pundit#policies
# @param record [Object] the object we're retrieving the policy for
# @return [Object] instance of policy class with query methods
# @since v0.1.0
def policy(record)
pundit.policy!(record)
end
# @!group Policy Scopes
# Retrieves the policy scope for the given record.
#
# @see https://github.com/varvet/pundit#scopes
# @param scope [Object] the object we're retrieving the policy scope for
# @param policy_scope_class [#resolve] the policy scope class we want to force use of
# @return [#resolve, nil] instance of scope class which can resolve to a scope
# @since v0.1.0
def policy_scope(scope, policy_scope_class: nil)
@_pundit_policy_scoped = true
policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
end
# Allow this action not to perform policy scoping.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @return [void]
# @see #verify_policy_scoped
# @since v1.0.0
def skip_policy_scope
@_pundit_policy_scoped = :skipped
end
# @return [Boolean] wether or not policy scoping has been performed
# @see #policy_scope
# @see #skip_policy_scope
# @since v1.0.0
def pundit_policy_scoped?
!!@_pundit_policy_scoped
end
# Raises an error if policy scoping has not been performed.
#
# Usually used as an `after_action` filter to prevent programmer error in
# forgetting to call {#policy_scope} or {#skip_policy_scope} in index
# actions.
#
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
# @return [void]
# @see #policy_scope
# @see #skip_policy_scope
# @since v0.2.1
def verify_policy_scoped
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
end
# rubocop:disable Naming/MemoizedInstanceVariableName
# Cache of policy scope. You should not rely on this method.
#
# @api private
# @since v1.0.0
def policy_scopes
@_pundit_policy_scopes ||= {}
end
# rubocop:enable Naming/MemoizedInstanceVariableName
# This was added to allow calling `policy_scope!` without flipping the
# `pundit_policy_scoped?` flag.
#
# It's used internally by `policy_scope`, as well as from the views
# when they call `policy_scope`. It works because views get their helper
# from {Pundit::Helper}.
#
# @note This also memoizes the instance with `scope` as the key.
# @see Pundit::Helper#policy_scope
# @api private
# @since v1.0.0
def pundit_policy_scope(scope)
policy_scopes[scope] ||= pundit.policy_scope!(scope)
end
private :pundit_policy_scope
# @!endgroup
# @!group Strong Parameters
# Retrieves a set of expected attributes from the policy.
#
# @example
# if @post.update(expected_attributes(@post))
# redirect_to @post
# else
# render :edit
# end
#
# @see https://github.com/varvet/pundit#strong-parameters
# @see https://guides.rubyonrails.org/action_controller_overview.html#expect
# @param record [Object] the object we're retrieving expected attributes for
# @param action [Symbol, String] the name of the action being performed on the record (e.g. `update`).
# If omitted then this defaults to the Rails controller action name.
# @param param_key [String] the key that the record would have in the params hash
# @return [Hash{String => Object}] the expected attributes
# @since v2.6.0
def expected_attributes(record, action: action_name, param_key: pundit_param_key(record))
policy = policy(record)
params.expect(param_key => policy.expected_attributes_for_action(action))
end
# @note This is provided as a hook for overrides.
# @param record [Object]
# @return [String] the key that the record would have in the params hash
# @since v2.6.0
def pundit_param_key(record)
PolicyFinder.new(record).param_key
end
# Retrieves a set of permitted attributes from the policy.
#
# Done by instantiating the policy class for the given record and calling
# `permitted_attributes` on it, or `permitted_attributes_for_{action}` if
# `action` is defined. It then infers what key the record should have in the
# params hash and retrieves the permitted attributes from the params hash
# under that key.
#
# @see https://github.com/varvet/pundit#strong-parameters
# @param record [Object] the object we're retrieving permitted attributes for
# @param action [Symbol, String] the name of the action being performed on the record (e.g. `update`).
# If omitted then this defaults to the Rails controller action name.
# @return [Hash{String => Object}] the permitted attributes
# @since v1.0.0
def permitted_attributes(record, action = action_name)
policy = policy(record)
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
"permitted_attributes_for_#{action}"
else
"permitted_attributes"
end
pundit_params_for(record).permit(*policy.public_send(method_name))
end
# Retrieves the params for the given record.
#
# @param record [Object] the object we're retrieving params for
# @return [ActionController::Parameters] the params
# @since v2.0.0
def pundit_params_for(record)
params.require(pundit_param_key(record))
end
# @!endgroup
end
end
================================================
FILE: lib/pundit/cache_store/legacy_store.rb
================================================
# frozen_string_literal: true
module Pundit
module CacheStore
# A cache store that uses only the record as a cache key, and ignores the user.
#
# The original cache mechanism used by Pundit.
#
# @api private
# @since v2.3.2
class LegacyStore
# @since v2.3.2
def initialize(hash = {})
@store = hash
end
# A cache store that uses only the record as a cache key, and ignores the user.
#
# @note `nil` results are not cached.
# @since v2.3.2
def fetch(user:, record:)
_ = user
@store[record] ||= yield
end
end
end
end
================================================
FILE: lib/pundit/cache_store/null_store.rb
================================================
# frozen_string_literal: true
module Pundit
module CacheStore
# A cache store that does not cache anything.
#
# Use `NullStore.instance` to get the singleton instance, it is thread-safe.
#
# @see Pundit::Context#initialize
# @api private
# @since v2.3.2
class NullStore
@instance = new
class << self
# @since v2.3.2
# @return [NullStore] the singleton instance
attr_reader :instance
end
# Always yields, does not cache anything.
# @yield
# @return [any] whatever the block returns.
# @since v2.3.2
def fetch(*, **)
yield
end
end
end
end
================================================
FILE: lib/pundit/cache_store.rb
================================================
# frozen_string_literal: true
module Pundit
# Namespace for cache store implementations.
#
# Cache stores are used to cache policy lookups, so you get the same policy
# instance for the same record.
# @since v2.3.2
module CacheStore
# @!group Cache Store Interface
# @!method fetch(user:, record:, &block)
# Looks up a stored policy or generate a new one.
#
# @since v2.3.2
# @note This is a method template, but the method does not exist in this module.
# @param user [Object] the user that initiated the action
# @param record [Object] the object being accessed
# @param block [Proc] the block to execute if missing
# @return [Object] the policy
# @!endgroup
end
end
================================================
FILE: lib/pundit/context.rb
================================================
# frozen_string_literal: true
module Pundit
# {Pundit::Context} is intended to be created once per request and user, and
# it is then used to perform authorization checks throughout the request.
#
# @example Using Sinatra
# helpers do
# def current_user = ...
#
# def pundit
# @pundit ||= Pundit::Context.new(user: current_user)
# end
# end
#
# get "/posts/:id" do |id|
# pundit.authorize(Post.find(id), query: :show?)
# end
#
# @example Using [Roda](https://roda.jeremyevans.net/index.html)
# route do |r|
# context = Pundit::Context.new(user:)
#
# r.get "posts", Integer do |id|
# context.authorize(Post.find(id), query: :show?)
# end
# end
#
# @since v2.3.2
class Context
# @see Pundit::Authorization#pundit
# @param user later passed to policies and scopes
# @param policy_cache [#fetch] cache store for policies (see e.g. {CacheStore::NullStore})
# @since v2.3.2
def initialize(user:, policy_cache: CacheStore::NullStore.instance)
@user = user
@policy_cache = policy_cache
end
# @api public
# @see #initialize
# @since v2.3.2
attr_reader :user
# @api private
# @see #initialize
# @since v2.3.2
attr_reader :policy_cache
# @!group Policies
# Retrieves the policy for the given record, initializing it with the
# record and user and finally throwing an error if the user is not
# authorized to perform the given action.
#
# @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
# @param policy_class [Class] the policy class we want to force use of
# @raise [NotAuthorizedError] if the given query method returned false
# @return [Object] Always returns the passed object record
# @since v2.3.2
def authorize(possibly_namespaced_record, query:, policy_class:)
record = pundit_model(possibly_namespaced_record)
policy = if policy_class
policy_class.new(user, record)
else
policy!(possibly_namespaced_record)
end
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
record
end
# Retrieves the policy for the given record.
#
# @see https://github.com/varvet/pundit#policies
# @param record [Object] the object we're retrieving the policy for
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Object, nil] instance of policy class with query methods
# @since v2.3.2
def policy(record)
cached_find(record, &:policy)
end
# Retrieves the policy for the given record, or raises if not found.
#
# @see https://github.com/varvet/pundit#policies
# @param record [Object] the object we're retrieving the policy for
# @raise [NotDefinedError] if the policy cannot be found
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Object] instance of policy class with query methods
# @since v2.3.2
def policy!(record)
cached_find(record, &:policy!)
end
# @!endgroup
# @!group Scopes
# Retrieves the policy scope for the given record.
#
# @see https://github.com/varvet/pundit#scopes
# @param scope [Object] the object we're retrieving the policy scope for
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
# @since v2.3.2
def policy_scope(scope)
policy_scope_class = policy_finder(scope).scope
return unless policy_scope_class
begin
policy_scope = policy_scope_class.new(user, pundit_model(scope))
rescue ArgumentError
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
end
policy_scope.resolve
end
# Retrieves the policy scope for the given record. Raises if not found.
#
# @see https://github.com/varvet/pundit#scopes
# @param scope [Object] the object we're retrieving the policy scope for
# @raise [NotDefinedError] if the policy scope cannot be found
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Scope{#resolve}] instance of scope class which can resolve to a scope
# @since v2.3.2
def policy_scope!(scope)
policy_scope_class = policy_finder(scope).scope!
begin
policy_scope = policy_scope_class.new(user, pundit_model(scope))
rescue ArgumentError
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
end
policy_scope.resolve
end
# @!endgroup
private
# @!group Private Helpers
# Finds a cached policy for the given record, or yields to find one.
#
# @api private
# @param record [Object] the object we're retrieving the policy for
# @yield a policy finder if no policy was cached
# @yieldparam [PolicyFinder] policy_finder
# @yieldreturn [#new(user, model)]
# @return [Policy, nil] an instantiated policy
# @raise [InvalidConstructorError] if policy can't be instantated
# @since v2.3.2
def cached_find(record)
policy_cache.fetch(user: user, record: record) do
klass = yield policy_finder(record)
next unless klass
model = pundit_model(record)
begin
klass.new(user, model)
rescue ArgumentError
raise InvalidConstructorError, "Invalid #<#{klass}> constructor is called"
end
end
end
# Return a policy finder for the given record.
#
# @api private
# @return [PolicyFinder]
# @since v2.3.2
def policy_finder(record)
PolicyFinder.new(record)
end
# Given a possibly namespaced record, return the actual record.
#
# @api private
# @since v2.3.2
def pundit_model(record)
record.is_a?(Array) ? record.last : record
end
end
end
================================================
FILE: lib/pundit/error.rb
================================================
# frozen_string_literal: true
module Pundit
# @api private
# @since v1.0.0
# To avoid name clashes with common Error naming when mixing in Pundit,
# keep it here with compact class style definition.
class Error < StandardError; end
# Error that will be raised when authorization has failed
# @since v0.1.0
class NotAuthorizedError < Error
# @see #initialize
# @since v0.2.3
attr_reader :query
# @see #initialize
# @since v0.2.3
attr_reader :record
# @see #initialize
# @since v0.2.3
attr_reader :policy
# @since v1.0.0
#
# @overload initialize(message)
# Create an error with a simple error message.
# @param [String] message A simple error message string.
#
# @overload initialize(options)
# Create an error with the specified attributes.
# @param [Hash] options The error options.
# @option options [String] :message Optional custom error message. Will default to a generalized message.
# @option options [Symbol] :query The name of the policy method that was checked.
# @option options [Object] :record The object that was being checked with the policy.
# @option options [Class] :policy The class of policy that was used for the check.
def initialize(options = {})
if options.is_a? String
message = options
else
@query = options[:query]
@record = options[:record]
@policy = options[:policy]
message = options.fetch(:message) do
record_name = record.is_a?(Class) ? record.to_s : "this #{record.class}"
"not allowed to #{policy.class}##{query} #{record_name}"
end
end
super(message)
end
end
# Error that will be raised if a policy or policy scope constructor is not called correctly.
# @since v2.0.0
class InvalidConstructorError < Error; end
# Error that will be raised if a controller action has not called the
# `authorize` or `skip_authorization` methods.
# @since v0.2.3
class AuthorizationNotPerformedError < Error; end
# Error that will be raised if a controller action has not called the
# `policy_scope` or `skip_policy_scope` methods.
# @since v0.3.0
class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end
# Error that will be raised if a policy or policy scope is not defined.
# @since v0.1.0
class NotDefinedError < Error; end
end
================================================
FILE: lib/pundit/helper.rb
================================================
# frozen_string_literal: true
module Pundit
# Rails view helpers, to allow a slightly different view-specific
# implementation of the methods in {Pundit::Authorization}.
#
# @api private
# @since v1.0.0
module Helper
# @see Pundit::Authorization#pundit_policy_scope
# @since v1.0.0
def policy_scope(scope)
pundit_policy_scope(scope)
end
end
end
================================================
FILE: lib/pundit/policy_finder.rb
================================================
# frozen_string_literal: true
# String#safe_constantize, String#demodulize, String#underscore, String#camelize
require "active_support/core_ext/string/inflections"
module Pundit
# Finds policy and scope classes for given object.
# @since v0.1.0
# @api public
# @example
# user = User.find(params[:id])
# finder = PolicyFinder.new(user)
# finder.policy #=> UserPolicy
# finder.scope #=> UserPolicy::Scope
#
class PolicyFinder
# A constant applied to the end of the class name to find the policy class.
#
# @api private
# @since v2.5.0
SUFFIX = "Policy"
# @see #initialize
# @since v0.1.0
attr_reader :object
# @param object [any] the object to find policy and scope classes for
# @since v0.1.0
def initialize(object)
@object = object
end
# @return [nil, Scope{#resolve}] scope class which can resolve to a scope
# @see https://github.com/varvet/pundit#scopes
# @example
# scope = finder.scope #=> UserPolicy::Scope
# scope.resolve #=> <#ActiveRecord::Relation ...>
#
# @since v0.1.0
def scope
"#{policy}::Scope".safe_constantize
end
# @return [nil, Class] policy class with query methods
# @see https://github.com/varvet/pundit#policies
# @example
# policy = finder.policy #=> UserPolicy
# policy.show? #=> true
# policy.update? #=> false
#
# @since v0.1.0
def policy
klass = find(object)
klass.is_a?(String) ? klass.safe_constantize : klass
end
# @return [Scope{#resolve}] scope class which can resolve to a scope
# @raise [NotDefinedError] if scope could not be determined
#
# @since v0.1.0
def scope!
scope or raise NotDefinedError, "unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`"
end
# @return [Class] policy class with query methods
# @raise [NotDefinedError] if policy could not be determined
#
# @since v0.1.0
def policy!
policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
end
# @return [String] the name of the key this object would have in a params hash
#
# @since v1.1.0
def param_key # rubocop:disable Metrics/AbcSize
model = object.is_a?(Array) ? object.last : object
if model.respond_to?(:model_name)
model.model_name.param_key.to_s
elsif model.is_a?(Class)
model.to_s.demodulize.underscore
else
model.class.to_s.demodulize.underscore
end
end
private
# Given an object, find the policy class name.
#
# Uses recursion to handle namespaces.
#
# @return [String, Class] the policy class, or its name.
# @since v0.2.0
def find(subject)
if subject.is_a?(Array)
modules = subject.dup
last = modules.pop
context = modules.map { |x| find_class_name(x) }.join("::")
[context, find(last)].join("::")
elsif subject.respond_to?(:policy_class)
subject.policy_class
elsif subject.class.respond_to?(:policy_class)
subject.class.policy_class
else
klass = find_class_name(subject)
"#{klass}#{SUFFIX}"
end
end
# Given an object, find its' class name.
#
# - Supports ActiveModel.
# - Supports regular classes.
# - Supports symbols.
# - Supports object instances.
#
# @return [String, Class] the class, or its name.
# @since v1.1.0
def find_class_name(subject)
if subject.respond_to?(:model_name)
subject.model_name
elsif subject.class.respond_to?(:model_name)
subject.class.model_name
elsif subject.is_a?(Class)
subject
elsif subject.is_a?(Symbol)
subject.to_s.camelize
else
subject.class
end
end
end
end
================================================
FILE: lib/pundit/railtie.rb
================================================
# frozen_string_literal: true
module Pundit
# @since v2.5.0
class Railtie < Rails::Railtie
if Rails.version.to_f >= 8.0
initializer "pundit.stats_directories" do
require "rails/code_statistics"
if Rails.root.join("app/policies").directory?
Rails::CodeStatistics.register_directory("Policies", "app/policies")
end
if Rails.root.join("test/policies").directory?
Rails::CodeStatistics.register_directory("Policy tests", "test/policies", test_directory: true)
end
end
end
end
end
================================================
FILE: lib/pundit/rspec.rb
================================================
# frozen_string_literal: true
require "pundit"
# Array#to_sentence
require "active_support/core_ext/array/conversions"
module Pundit
# Namespace for Pundit's RSpec integration.
# @since v0.1.0
module RSpec
# Namespace for Pundit's RSpec matchers.
module Matchers
extend ::RSpec::Matchers::DSL
# @!method description=(description)
class << self
# Used to build a suitable description for the Pundit `permit` matcher.
# @api public
# @param value [String, Proc]
# @example
# Pundit::RSpec::Matchers.description = ->(user, record) do
# "permit user with role #{user.role} to access record with ID #{record.id}"
# end
attr_writer :description
# Used to retrieve a suitable description for the Pundit `permit` matcher.
# @api private
# @private
def description(user, record)
return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
@description
end
end
# rubocop:disable Metrics/BlockLength
matcher :permit do |user, record|
match_proc = lambda do |policy|
@violating_permissions = permissions.find_all do |permission|
!policy.new(user, record).public_send(permission)
end
@violating_permissions.empty?
end
match_when_negated_proc = lambda do |policy|
@violating_permissions = permissions.find_all do |permission|
policy.new(user, record).public_send(permission)
end
@violating_permissions.empty?
end
failure_message_proc = lambda do |policy|
"Expected #{policy} to grant #{permissions.to_sentence} on " \
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} not granted"
end
failure_message_when_negated_proc = lambda do |policy|
"Expected #{policy} not to grant #{permissions.to_sentence} on " \
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} granted"
end
def was_or_were
if @violating_permissions.count > 1
"were"
else
"was"
end
end
description do
Pundit::RSpec::Matchers.description(user, record) || super()
end
if respond_to?(:match_when_negated)
match(&match_proc)
match_when_negated(&match_when_negated_proc)
failure_message(&failure_message_proc)
failure_message_when_negated(&failure_message_when_negated_proc)
else
# :nocov:
# Compatibility with RSpec < 3.0, released 2014-06-01.
match_for_should(&match_proc)
match_for_should_not(&match_when_negated_proc)
failure_message_for_should(&failure_message_proc)
failure_message_for_should_not(&failure_message_when_negated_proc)
# :nocov:
end
if ::RSpec.respond_to?(:current_example)
def current_example
::RSpec.current_example
end
else
# :nocov:
# Compatibility with RSpec < 3.0, released 2014-06-01.
def current_example
example
end
# :nocov:
end
def permissions
current_example.metadata.fetch(:permissions) do
raise KeyError, <<~ERROR.strip
No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`?
ERROR
end
end
end
# rubocop:enable Metrics/BlockLength
end
# Mixed in to all policy example groups to provide a DSL.
module DSL
# @example
# describe PostPolicy do
# permissions :show?, :update? do
# it { is_expected.to permit(user, own_post) }
# end
# end
#
# @example focused example group
# describe PostPolicy do
# permissions :show?, :update?, :focus do
# it { is_expected.to permit(user, own_post) }
# end
# end
#
# @param list [Symbol, Array<Symbol>] a permission to describe
# @return [void]
def permissions(*list, &block)
metadata = {permissions: list, caller: caller}
if list.last == :focus
list.pop
metadata[:focus] = true
end
description = list.to_sentence
describe(description, metadata) { instance_eval(&block) }
end
end
# Mixed in to all policy example groups.
#
# @private not useful
module PolicyExampleGroup
include Pundit::RSpec::Matchers
def self.included(base)
base.metadata[:type] = :policy
base.extend Pundit::RSpec::DSL
super
end
end
end
end
RSpec.configure do |config|
config.include(Pundit::RSpec::PolicyExampleGroup, file_path: %r{spec/policies})
config.include(Pundit::RSpec::PolicyExampleGroup, type: :policy)
end
================================================
FILE: lib/pundit/version.rb
================================================
# frozen_string_literal: true
module Pundit
# The current version of Pundit.
VERSION = "2.5.2"
end
================================================
FILE: lib/pundit.rb
================================================
# frozen_string_literal: true
require "active_support"
require "pundit/version"
require "pundit/error"
require "pundit/policy_finder"
require "pundit/context"
require "pundit/authorization"
require "pundit/helper"
require "pundit/cache_store"
require "pundit/cache_store/null_store"
require "pundit/cache_store/legacy_store"
# :nocov:
require "pundit/railtie" if defined?(Rails)
# :nocov:
# Hello? Yes, this is Pundit.
#
# @api public
module Pundit
# @api private
# @since v1.0.0
# @deprecated See {Pundit::PolicyFinder}
SUFFIX = Pundit::PolicyFinder::SUFFIX
# @api private
# @private
# @since v0.1.0
module Generators; end
def self.included(base)
location = caller_locations(1, 1).first
warn <<~WARNING
'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
(called from #{location.label} at #{location.path}:#{location.lineno})
WARNING
base.include Authorization
end
class << self
# @see Pundit::Context#authorize
# @since v1.0.0
def authorize(user, record, query, policy_class: nil, cache: nil)
context = if cache
policy_cache = CacheStore::LegacyStore.new(cache)
Context.new(user: user, policy_cache: policy_cache)
else
Context.new(user: user)
end
context.authorize(record, query: query, policy_class: policy_class)
end
# @see Pundit::Context#policy_scope
# @since v0.1.0
def policy_scope(user, *args, **kwargs, &block)
Context.new(user: user).policy_scope(*args, **kwargs, &block)
end
# @see Pundit::Context#policy_scope!
# @since v0.1.0
def policy_scope!(user, *args, **kwargs, &block)
Context.new(user: user).policy_scope!(*args, **kwargs, &block)
end
# @see Pundit::Context#policy
# @since v0.1.0
def policy(user, *args, **kwargs, &block)
Context.new(user: user).policy(*args, **kwargs, &block)
end
# @see Pundit::Context#policy!
# @since v0.1.0
def policy!(user, *args, **kwargs, &block)
Context.new(user: user).policy!(*args, **kwargs, &block)
end
end
end
================================================
FILE: pundit.gemspec
================================================
# frozen_string_literal: true
require_relative "lib/pundit/version"
Gem::Specification.new do |gem|
gem.name = "pundit"
gem.version = Pundit::VERSION
gem.authors = ["Jonas Nicklas", "Varvet AB"]
gem.email = ["jonas.nicklas@gmail.com", "info@varvet.com"]
gem.description = "Object oriented authorization for Rails applications"
gem.summary = "OO authorization for Rails"
gem.homepage = "https://github.com/varvet/pundit"
gem.license = "MIT"
Dir.chdir(__dir__) do
gem.files = `git ls-files -z`.split("\x0").select do |f|
f.start_with?("lib/", "README", "SECURITY", "LICENSE", "CHANGELOG", "CONTRIBUTING", "config/rubocop-rspec.yml")
end
end
gem.require_paths = ["lib"]
gem.metadata = {
"rubygems_mfa_required" => "true",
"bug_tracker_uri" => "https://github.com/varvet/pundit/issues",
"changelog_uri" => "https://github.com/varvet/pundit/blob/main/CHANGELOG.md",
"documentation_uri" => "https://github.com/varvet/pundit/blob/main/README.md",
"homepage_uri" => "https://github.com/varvet/pundit",
"source_code_uri" => "https://github.com/varvet/pundit"
}
gem.add_dependency "activesupport", ">= 3.0.0"
end
================================================
FILE: spec/authorization_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
require "action_controller"
RSpec.describe Pundit::Authorization do
def to_params(*args, **kwargs, &block)
ActionController::Parameters.new(*args, **kwargs, &block)
end
let(:controller) { Controller.new(user, "update", to_params({})) }
let(:user) { double("user") }
let(:post) { Post.new(user) }
let(:comment) { Comment.new }
let(:article) { Article.new }
let(:article_tag) { ArticleTag.new }
let(:wiki) { Wiki.new }
describe "#verify_authorized" do
it "does nothing when authorized" do
controller.authorize(post)
controller.verify_authorized
end
it "raises an exception when not authorized" do
expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
end
end
describe "#verify_policy_scoped" do
it "does nothing when policy_scope is used" do
controller.policy_scope(Post)
controller.verify_policy_scoped
end
it "raises an exception when policy_scope is not used" do
expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
end
end
describe "#pundit_policy_authorized?" do
it "is true when authorized" do
controller.authorize(post)
expect(controller.pundit_policy_authorized?).to be true
end
it "is false when not authorized" do
expect(controller.pundit_policy_authorized?).to be false
end
end
describe "#pundit_policy_scoped?" do
it "is true when policy_scope is used" do
controller.policy_scope(Post)
expect(controller.pundit_policy_scoped?).to be true
end
it "is false when policy scope is not used" do
expect(controller.pundit_policy_scoped?).to be false
end
end
describe "#authorize" do
it "infers the policy name and authorizes based on it" do
expect(controller.authorize(post)).to be_truthy
end
it "returns the record on successful authorization" do
expect(controller.authorize(post)).to eq(post)
end
it "returns the record when passed record with namespace " do
expect(controller.authorize([:project, comment], :update?)).to eq(comment)
end
it "returns the record when passed record with nested namespace " do
expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment)
end
it "returns the policy name symbol when passed record with headless policy" do
expect(controller.authorize(:publication, :create?)).to eq(:publication)
end
it "returns the class when passed record not a particular instance" do
expect(controller.authorize(Post, :show?)).to eq(Post)
end
it "can be given a different permission to check" do
expect(controller.authorize(post, :show?)).to be_truthy
expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
end
it "can be given a different policy class" do
expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
end
it "works with anonymous class policies" do
expect(controller.authorize(article_tag, :show?)).to be_truthy
expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
end
it "throws an exception when the permission check fails" do
expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
end
it "throws an exception when a policy cannot be found" do
expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
end
it "caches the policy" do
expect(controller.policies[post]).to be_nil
controller.authorize(post)
expect(controller.policies[post]).not_to be_nil
end
it "raises an error when the given record is nil" do
expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
end
it "raises an error with a invalid policy constructor" do
expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
end
end
describe "#skip_authorization" do
it "disables authorization verification" do
controller.skip_authorization
expect { controller.verify_authorized }.not_to raise_error
end
end
describe "#skip_policy_scope" do
it "disables policy scope verification" do
controller.skip_policy_scope
expect { controller.verify_policy_scoped }.not_to raise_error
end
end
describe "#pundit_user" do
it "returns the same thing as current_user" do
expect(controller.pundit_user).to eq controller.current_user
end
end
describe "#policy" do
it "returns an instantiated policy" do
policy = controller.policy(post)
expect(policy.user).to eq user
expect(policy.post).to eq post
end
it "throws an exception if the given policy can't be found" do
expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
end
it "raises an error with a invalid policy constructor" do
expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
end
it "allows policy to be injected" do
new_policy = double
controller.policies[post] = new_policy
expect(controller.policy(post)).to eq new_policy
end
end
describe "#policy_scope" do
it "returns an instantiated policy scope" do
expect(controller.policy_scope(Post)).to eq :published
end
it "allows policy scope class to be overridden" do
expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
end
it "throws an exception if the given policy can't be found" do
expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
end
it "raises an error with a invalid policy scope constructor" do
expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
end
it "allows policy_scope to be injected" do
new_scope = double
controller.policy_scopes[Post] = new_scope
expect(controller.policy_scope(Post)).to eq new_scope
end
end
describe "#permitted_attributes" do
it "checks policy for permitted attributes" do
params = to_params(
post: {
title: "Hello",
votes: 5,
admin: true
}
)
action = "update"
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
"title" => "Hello",
"votes" => 5
)
expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
end
it "checks policy for permitted attributes for record of a ActiveModel type" do
customer_post = Customer::Post.new(user)
params = to_params(
customer_post: {
title: "Hello",
votes: 5,
admin: true
}
)
action = "update"
expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
"title" => "Hello",
"votes" => 5
)
expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
"votes" => 5
)
end
it "goes through the policy cache" do
params = to_params(post: {title: "Hello"})
user = double
post = Post.new(user)
controller = Controller.new(user, "update", params)
expect do
expect(controller.permitted_attributes(post)).to be_truthy
expect(controller.permitted_attributes(post)).to be_truthy
end.to change { PostPolicy.instances }.by(1)
end
end
describe "#permitted_attributes_for_action" do
it "is checked if it is defined in the policy" do
params = to_params(
post: {
title: "Hello",
body: "blah",
votes: 5,
admin: true
}
)
action = "revise"
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
end
it "can be explicitly set" do
params = to_params(
post: {
title: "Hello",
body: "blah",
votes: 5,
admin: true
}
)
action = "update"
expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
end
end
if ActionController::Parameters.method_defined?(:expect)
describe "#expected_attributes" do
it "checks policy for expected attributes" do
params = to_params(
post: {
title: "Hello",
votes: 5,
admin: true
}
)
action = "update"
expect(Controller.new(user, action, params).expected_attributes(post).to_h).to eq(
"title" => "Hello",
"votes" => 5
)
expect(Controller.new(double, action, params).expected_attributes(post).to_h).to eq("votes" => 5)
end
it "checks policy for expected attributes for record of a ActiveModel type" do
customer_post = Customer::Post.new(user)
params = to_params(
customer_post: {
title: "Hello",
votes: 5,
admin: true
}
)
action = "update"
expect(Controller.new(user, action, params).expected_attributes(customer_post).to_h).to eq(
"title" => "Hello",
"votes" => 5
)
expect(Controller.new(double, action, params).expected_attributes(customer_post).to_h).to eq(
"votes" => 5
)
end
it "goes through the policy cache" do
params = to_params(post: {title: "Hello"})
user = double
post = Post.new(user)
controller = Controller.new(user, "update", params)
expect do
expect(controller.expected_attributes(post)).to be_truthy
expect(controller.expected_attributes(post)).to be_truthy
end.to change { PostPolicy.instances }.by(1)
end
end
context "action-specific expected attributes" do
it "is checked if it is defined in the policy" do
params = to_params(
post: {
title: "Hello",
body: "blah",
votes: 5,
admin: true
}
)
action = "revise"
expect(Controller.new(user, action, params).expected_attributes(post).to_h).to eq("body" => "blah")
end
it "can be explicitly set" do
params = to_params(
post: {
title: "Hello",
body: "blah",
votes: 5,
admin: true
}
)
action = "update"
controller = Controller.new(user, action, params)
expect(controller.expected_attributes(post, action: :revise).to_h).to eq("body" => "blah")
end
end
it "can be retrieved with an explicit param key" do
params = to_params(admin_post: {title: "Hello"})
action = "update"
controller = Controller.new(user, action, params)
expect(controller.expected_attributes(post, param_key: "admin_post").to_h).to eq("title" => "Hello")
end
end
describe "#pundit_reset!" do
it "allows authorize to react to a user change" do
expect(controller.authorize(post)).to be_truthy
controller.current_user = double
controller.pundit_reset!
expect { controller.authorize(post) }.to raise_error(Pundit::NotAuthorizedError)
end
it "allows policy to react to a user change" do
expect(controller.policy(DummyCurrentUser).user).to be user
new_user = double("new user")
controller.current_user = new_user
controller.pundit_reset!
expect(controller.policy(DummyCurrentUser).user).to be new_user
end
it "allows policy scope to react to a user change" do
expect(controller.policy_scope(DummyCurrentUser)).to be user
new_user = double("new user")
controller.current_user = new_user
controller.pundit_reset!
expect(controller.policy_scope(DummyCurrentUser)).to be new_user
end
it "resets the pundit context" do
expect(controller.pundit.user).to be(user)
new_user = double
controller.current_user = new_user
expect { controller.pundit_reset! }.to change { controller.pundit.user }.from(user).to(new_user)
end
it "clears pundit_policy_authorized? flag" do
expect(controller.pundit_policy_authorized?).to be false
controller.skip_authorization
expect(controller.pundit_policy_authorized?).to be true
controller.pundit_reset!
expect(controller.pundit_policy_authorized?).to be false
end
it "clears pundit_policy_scoped? flag" do
expect(controller.pundit_policy_scoped?).to be false
controller.skip_policy_scope
expect(controller.pundit_policy_scoped?).to be true
controller.pundit_reset!
expect(controller.pundit_policy_scoped?).to be false
end
end
end
================================================
FILE: spec/generators_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
require "tmpdir"
require "rails/generators"
require "generators/pundit/install/install_generator"
require "generators/pundit/policy/policy_generator"
RSpec.describe "generators" do
before(:all) do
@tmpdir = Dir.mktmpdir
Dir.chdir(@tmpdir) do
Pundit::Generators::InstallGenerator.new([], {quiet: true}).invoke_all
Pundit::Generators::PolicyGenerator.new(%w[Widget], {quiet: true}).invoke_all
require "./app/policies/application_policy"
require "./app/policies/widget_policy"
end
end
after(:all) do
FileUtils.remove_entry(@tmpdir)
end
describe "WidgetPolicy", type: :policy do
permissions :index?, :show?, :create?, :new?, :update?, :edit?, :destroy? do
it "has safe defaults" do
expect(WidgetPolicy).not_to permit(double("User"), double("Widget"))
end
end
describe "WidgetPolicy::Scope" do
describe "#resolve" do
it "raises a descriptive error" do
scope = WidgetPolicy::Scope.new(double("User"), double("User.all"))
expect { scope.resolve }.to raise_error(NoMethodError, /WidgetPolicy::Scope/)
end
end
end
end
end
================================================
FILE: spec/policies/post_policy_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
RSpec.describe PostPolicy do
let(:user) { double }
let(:own_post) { double(user: user) }
let(:other_post) { double(user: double) }
subject { described_class }
permissions :update?, :show? do
it "is successful when all permissions match" do
is_expected.to permit(user, own_post)
end
it "fails when any permissions do not match" do
expect do
is_expected.to permit(user, other_post)
end.to raise_error(RSpec::Expectations::ExpectationNotMetError)
end
it "uses the default description if not overridden" do
expect(permit(user, own_post).description).to eq("permit #{user.inspect} and #{own_post.inspect}")
end
context "when the matcher description is overridden" do
after do
Pundit::RSpec::Matchers.description = nil
end
it "sets a custom matcher description with a Proc" do
allow(user).to receive(:role).and_return("default_role")
allow(own_post).to receive(:id).and_return(1)
Pundit::RSpec::Matchers.description = lambda { |user, record|
"permit user with role #{user.role} to access record with ID #{record.id}"
}
description = permit(user, own_post).description
expect(description).to eq("permit user with role default_role to access record with ID 1")
end
it "sets a custom matcher description with a string" do
Pundit::RSpec::Matchers.description = "permit user"
expect(permit(user, own_post).description).to eq("permit user")
end
end
end
end
================================================
FILE: spec/policy_finder_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Pundit::PolicyFinder do
let(:user) { double }
let(:post) { Post.new(user) }
let(:comment) { CommentFourFiveSix.new }
let(:article) { Article.new }
describe "SUFFIX" do
specify { expect(described_class::SUFFIX).to eq "Policy" }
specify { expect(Pundit::SUFFIX).to eq(described_class::SUFFIX) }
end
describe "#scope" do
subject { described_class.new(post) }
it "returns a policy scope" do
expect(subject.scope).to eq PostPolicy::Scope
end
context "policy is nil" do
it "returns nil" do
allow(subject).to receive(:policy).and_return nil
expect(subject.scope).to eq nil
end
end
end
describe "#policy" do
context "with an instance" do
it "returns the associated policy" do
object = described_class.new(post)
expect(object.policy).to eq PostPolicy
end
end
context "with an array of symbols" do
it "returns the associated namespaced policy" do
object = described_class.new(%i[project post])
expect(object.policy).to eq Project::PostPolicy
end
end
context "with an array of a symbol and an instance" do
it "returns the associated namespaced policy" do
object = described_class.new([:project, post])
expect(object.policy).to eq Project::PostPolicy
end
end
context "with an array of a symbol and a class with a specified policy class" do
it "returns the associated namespaced policy" do
object = described_class.new([:project, Customer::Post])
expect(object.policy).to eq Project::PostPolicy
end
end
context "with an array of a symbol and a class with a specified model name" do
it "returns the associated namespaced policy" do
object = described_class.new([:project, CommentsRelation])
expect(object.policy).to eq Project::CommentPolicy
end
end
context "with a class" do
it "returns the associated policy" do
object = described_class.new(Post)
expect(object.policy).to eq PostPolicy
end
end
context "with a class which has a specified policy class" do
it "returns the associated policy" do
object = described_class.new(Customer::Post)
expect(object.policy).to eq PostPolicy
end
end
context "with an instance which has a specified policy class" do
it "returns the associated policy" do
object = described_class.new(Customer::Post.new(user))
expect(object.policy).to eq PostPolicy
end
end
context "with a class which has a specified model name" do
it "returns the associated policy" do
object = described_class.new(CommentsRelation)
expect(object.policy).to eq CommentPolicy
end
end
context "with an instance which has a specified policy class" do
it "returns the associated policy" do
object = described_class.new(CommentsRelation.new)
expect(object.policy).to eq CommentPolicy
end
end
context "with nil" do
it "returns a NilClassPolicy" do
object = described_class.new(nil)
expect(object.policy).to eq NilClassPolicy
end
end
context "with a class that doesn't have an associated policy" do
it "returns nil" do
object = described_class.new(Foo)
expect(object.policy).to eq nil
end
end
end
describe "#scope!" do
context "@object is nil" do
subject { described_class.new(nil) }
it "returns the NilClass policy's scope class" do
expect(subject.scope!).to eq NilClassPolicy::Scope
end
end
context "@object is defined" do
subject { described_class.new(post) }
it "returns the scope" do
expect(subject.scope!).to eq PostPolicy::Scope
end
end
end
describe "#param_key" do
context "object responds to model_name" do
subject { described_class.new(comment) }
it "returns the param_key" do
expect(subject.object).to respond_to(:model_name)
expect(subject.param_key).to eq "comment_four_five_six"
end
end
context "object is a class" do
subject { described_class.new(Article) }
it "returns the param_key" do
expect(subject.object).not_to respond_to(:model_name)
expect(subject.object).to be_a Class
expect(subject.param_key).to eq "article"
end
end
context "object is an instance of a class" do
subject { described_class.new(article) }
it "returns the param_key" do
expect(subject.object).not_to respond_to(:model_name)
expect(subject.object).not_to be_a Class
expect(subject.object).to be_an_instance_of Article
expect(subject.param_key).to eq "article"
end
end
context "object is an array" do
subject { described_class.new([:project, article]) }
it "returns the param_key for the last element of the array" do
expect(subject.object).not_to respond_to(:model_name)
expect(subject.object).not_to be_a Class
expect(subject.object).to be_an_instance_of Array
expect(subject.param_key).to eq "article"
end
end
end
end
================================================
FILE: spec/pundit/helper_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Pundit::Helper do
let(:user) { double }
let(:controller) { Controller.new(user, "update", double) }
let(:view) { Controller::View.new(controller) }
describe "#policy_scope" do
it "doesn't flip pundit_policy_scoped?" do
scoped = view.policy_scope(Post)
expect(scoped).to be(Post.published)
expect(controller).not_to be_pundit_policy_scoped
end
end
end
================================================
FILE: spec/pundit_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Pundit do
let(:user) { double }
let(:post) { Post.new(user) }
let(:customer_post) { Customer::Post.new(user) }
let(:post_four_five_six) { PostFourFiveSix.new(user) }
let(:comment) { Comment.new }
let(:comment_four_five_six) { CommentFourFiveSix.new }
let(:article) { Article.new }
let(:artificial_blog) { ArtificialBlog.new }
let(:article_tag) { ArticleTag.new }
let(:comments_relation) { CommentsRelation.new(empty: false) }
let(:empty_comments_relation) { CommentsRelation.new(empty: true) }
let(:tag_four_five_six) { ProjectOneTwoThree::TagFourFiveSix.new(user) }
let(:avatar_four_five_six) { ProjectOneTwoThree::AvatarFourFiveSix.new }
let(:wiki) { Wiki.new }
describe ".authorize" do
it "infers the policy and authorizes based on it" do
expect(Pundit.authorize(user, post, :update?)).to be_truthy
end
it "returns the record on successful authorization" do
expect(Pundit.authorize(user, post, :update?)).to eq(post)
end
it "returns the record when passed record with namespace " do
expect(Pundit.authorize(user, [:project, comment], :update?)).to eq(comment)
end
it "returns the record when passed record with nested namespace " do
expect(Pundit.authorize(user, [:project, :admin, comment], :update?)).to eq(comment)
end
it "returns the policy name symbol when passed record with headless policy" do
expect(Pundit.authorize(user, :publication, :create?)).to eq(:publication)
end
it "returns the class when passed record not a particular instance" do
expect(Pundit.authorize(user, Post, :show?)).to eq(Post)
end
it "works with anonymous class policies" do
expect(Pundit.authorize(user, article_tag, :show?)).to be_truthy
expect { Pundit.authorize(user, article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
end
it "raises an error with the policy, query and record" do
# rubocop:disable Style/MultilineBlockChain
expect do
Pundit.authorize(user, post, :destroy?)
end.to raise_error(Pundit::NotAuthorizedError, "not allowed to PostPolicy#destroy? this Post") do |error|
expect(error.query).to eq :destroy?
expect(error.record).to eq post
expect(error.policy).to have_attributes(
user: user,
record: post
)
expect(error.policy).to be_a(PostPolicy)
end
# rubocop:enable Style/MultilineBlockChain
end
it "raises an error with the policy, query and record when the record is namespaced" do
# rubocop:disable Style/MultilineBlockChain
expect do
Pundit.authorize(user, [:project, :admin, comment], :destroy?)
end.to raise_error(Pundit::NotAuthorizedError,
"not allowed to Project::Admin::CommentPolicy#destroy? this Comment") do |error|
expect(error.query).to eq :destroy?
expect(error.record).to eq comment
expect(error.policy).to have_attributes(
user: user,
record: comment
)
expect(error.policy).to be_a(Project::Admin::CommentPolicy)
end
# rubocop:enable Style/MultilineBlockChain
end
it "raises an error with the policy, query and the class name when a Class is given" do
# rubocop:disable Style/MultilineBlockChain
expect do
Pundit.authorize(user, Post, :destroy?)
end.to raise_error(Pundit::NotAuthorizedError, "not allowed to PostPolicy#destroy? Post") do |error|
expect(error.query).to eq :destroy?
expect(error.record).to eq Post
expect(error.policy).to have_attributes(
user: user,
record: Post
)
expect(error.policy).to be_a(PostPolicy)
end
# rubocop:enable Style/MultilineBlockChain
end
it "raises an error with a invalid policy constructor" do
expect do
Pundit.authorize(user, wiki, :update?)
end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy> constructor is called")
end
context "when passed a policy class" do
it "uses the passed policy class" do
expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy
end
# This is documenting past behaviour.
it "doesn't cache the policy class" do
cache = {}
expect do
Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy, cache: cache)
Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy, cache: cache)
end.to change { PublicationPolicy.instances }.by(2)
end
end
context "when passed a policy class while simultaenously passing a namespace" do
it "uses the passed policy class" do
expect(PublicationPolicy).to receive(:new).with(user, comment).and_call_original
expect(Pundit.authorize(user, [:project, comment], :create?, policy_class: PublicationPolicy)).to be_truthy
end
end
context "when passed an explicit cache" do
it "uses the hash assignment interface on the cache" do
custom_cache = CustomCache.new
Pundit.authorize(user, post, :update?, cache: custom_cache)
expect(custom_cache.to_h).to match({
post => kind_of(PostPolicy)
})
end
end
end
describe ".policy_scope" do
it "returns an instantiated policy scope given a plain model class" do
expect(Pundit.policy_scope(user, Post)).to eq :published
end
it "returns an instantiated policy scope given an active model class" do
expect(Pundit.policy_scope(user, Comment)).to eq CommentScope.new(Comment)
end
it "returns an instantiated policy scope given an active record relation" do
expect(Pundit.policy_scope(user, comments_relation)).to eq CommentScope.new(comments_relation)
end
it "returns an instantiated policy scope given an empty active record relation" do
expect(Pundit.policy_scope(user, empty_comments_relation)).to eq CommentScope.new(empty_comments_relation)
end
it "returns an instantiated policy scope given an array of a symbol and plain model class" do
expect(Pundit.policy_scope(user, [:project, Post])).to eq :read
end
it "returns an instantiated policy scope given an array of a symbol and active model class" do
expect(Pundit.policy_scope(user, [:project, Comment])).to eq Comment
end
it "returns nil if the given policy scope can't be found" do
expect(Pundit.policy_scope(user, Article)).to be_nil
end
it "raises an exception if nil object given" do
expect { Pundit.policy_scope(user, nil) }.to raise_error(Pundit::NotDefinedError)
end
it "raises an error with a invalid policy scope constructor" do
expect do
Pundit.policy_scope(user, Wiki)
end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy::Scope> constructor is called")
end
it "raises an original error with a policy scope that contains error" do
expect do
Pundit.policy_scope(user, DefaultScopeContainsError)
end.to raise_error(RuntimeError, "This is an arbitrary error that should bubble up")
end
end
describe ".policy_scope!" do
it "returns an instantiated policy scope given a plain model class" do
expect(Pundit.policy_scope!(user, Post)).to eq :published
end
it "returns an instantiated policy scope given an active model class" do
expect(Pundit.policy_scope!(user, Comment)).to eq CommentScope.new(Comment)
end
it "throws an exception if the given policy scope can't be found" do
expect { Pundit.policy_scope!(user, Article) }.to raise_error(Pundit::NotDefinedError)
end
it "throws an exception if the given policy scope can't be found" do
expect { Pundit.policy_scope!(user, ArticleTag) }.to raise_error(Pundit::NotDefinedError)
end
it "throws an exception if the given policy scope is nil" do
expect do
Pundit.policy_scope!(user, nil)
end.to raise_error(Pundit::NotDefinedError, "Cannot scope NilClass")
end
it "returns an instantiated policy scope given an array of a symbol and plain model class" do
expect(Pundit.policy_scope!(user, [:project, Post])).to eq :read
end
it "returns an instantiated policy scope given an array of a symbol and active model class" do
expect(Pundit.policy_scope!(user, [:project, Comment])).to eq Comment
end
it "raises an error with a invalid policy scope constructor" do
expect do
Pundit.policy_scope(user, Wiki)
end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy::Scope> constructor is called")
end
end
describe ".policy" do
it "returns an instantiated policy given a plain model instance" do
policy = Pundit.policy(user, post)
expect(policy.user).to eq user
expect(policy.post).to eq post
end
it "returns an instantiated policy given an active model instance" do
policy = Pundit.policy(user, comment)
expect(policy.user).to eq user
expect(policy.comment).to eq comment
end
it "returns an instantiated policy given a plain model class" do
policy = Pundit.policy(user, Post)
expect(policy.user).to eq user
expect(policy.post).to eq Post
end
it "returns an instantiated policy given an active model class" do
policy = Pundit.policy(user, Comment)
expect(policy.user).to eq user
expect(policy.comment).to eq Comment
end
it "returns an instantiated policy given a symbol" do
policy = Pundit.policy(user, :criteria)
expect(policy.class).to eq CriteriaPolicy
expect(policy.user).to eq user
expect(policy.criteria).to eq :criteria
end
it "returns an instantiated policy given an array of symbols" do
policy = Pundit.policy(user, %i[project criteria])
expect(policy.class).to eq Project::CriteriaPolicy
expect(policy.user).to eq user
expect(policy.criteria).to eq :criteria
end
it "returns an instantiated policy given an array of a symbol and plain model instance" do
policy = Pundit.policy(user, [:project, post])
expect(policy.class).to eq Project::PostPolicy
expect(policy.user).to eq user
expect(policy.post).to eq post
end
it "returns an instantiated policy given an array of a symbol and a model instance with policy_class override" do
policy = Pundit.policy(user, [:project, customer_post])
expect(policy.class).to eq Project::PostPolicy
expect(policy.user).to eq user
expect(policy.post).to eq customer_post
end
it "returns an instantiated policy given an array of a symbol and an active model instance" do
policy = Pundit.policy(user, [:project, comment])
expect(policy.class).to eq Project::CommentPolicy
expect(policy.user).to eq user
expect(policy.comment).to eq comment
end
it "returns an instantiated policy given an array of a symbol and a plain model class" do
policy = Pundit.policy(user, [:project, Post])
expect(policy.class).to eq Project::PostPolicy
expect(policy.user).to eq user
expect(policy.post).to eq Post
end
it "raises an error with a invalid policy constructor" do
expect do
Pundit.policy(user, Wiki)
end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy> constructor is called")
end
it "returns an instantiated policy given an array of a symbol and an active model class" do
policy = Pundit.policy(user, [:project, Comment])
expect(policy.class).to eq Project::CommentPolicy
expect(policy.user).to eq user
expect(policy.comment).to eq Comment
end
it "returns an instantiated policy given an array of a symbol and a class with policy_class override" do
policy = Pundit.policy(user, [:project, Customer::Post])
expect(policy.class).to eq Project::PostPolicy
expect(policy.user).to eq user
expect(policy.post).to eq Customer::Post
end
it "returns correct policy class for an array of a multi-word symbols" do
policy = Pundit.policy(user, %i[project_one_two_three criteria_four_five_six])
expect(policy.class).to eq ProjectOneTwoThree::CriteriaFourFiveSixPolicy
end
it "returns correct policy class for an array of a multi-word symbol and a multi-word plain model instance" do
policy = Pundit.policy(user, [:project_one_two_three, post_four_five_six])
expect(policy.class).to eq ProjectOneTwoThree::PostFourFiveSixPolicy
end
it "returns correct policy class for an array of a multi-word symbol and a multi-word active model instance" do
policy = Pundit.policy(user, [:project_one_two_three, comment_four_five_six])
expect(policy.class).to eq ProjectOneTwoThree::CommentFourFiveSixPolicy
end
it "returns correct policy class for an array of a multi-word symbol and a multi-word plain model class" do
policy = Pundit.policy(user, [:project_one_two_three, PostFourFiveSix])
expect(policy.class).to eq ProjectOneTwoThree::PostFourFiveSixPolicy
end
it "returns correct policy class for an array of a multi-word symbol and a multi-word active model class" do
policy = Pundit.policy(user, [:project_one_two_three, CommentFourFiveSix])
expect(policy.class).to eq ProjectOneTwoThree::CommentFourFiveSixPolicy
end
it "returns correct policy class for a multi-word scoped plain model class" do
policy = Pundit.policy(user, ProjectOneTwoThree::TagFourFiveSix)
expect(policy.class).to eq ProjectOneTwoThree::TagFourFiveSixPolicy
end
it "returns correct policy class for a multi-word scoped plain model instance" do
policy = Pundit.policy(user, tag_four_five_six)
expect(policy.class).to eq ProjectOneTwoThree::TagFourFiveSixPolicy
end
it "returns correct policy class for a multi-word scoped active model class" do
policy = Pundit.policy(user, ProjectOneTwoThree::AvatarFourFiveSix)
expect(policy.class).to eq ProjectOneTwoThree::AvatarFourFiveSixPolicy
end
it "returns correct policy class for a multi-word scoped active model instance" do
policy = Pundit.policy(user, avatar_four_five_six)
expect(policy.class).to eq ProjectOneTwoThree::AvatarFourFiveSixPolicy
end
it "returns nil if the given policy can't be found" do
expect(Pundit.policy(user, article)).to be_nil
expect(Pundit.policy(user, Article)).to be_nil
end
it "returns the specified NilClassPolicy for nil" do
expect(Pundit.policy(user, nil)).to be_a NilClassPolicy
end
describe "with .policy_class set on the model" do
it "returns an instantiated policy given a plain model instance" do
policy = Pundit.policy(user, artificial_blog)
expect(policy.user).to eq user
expect(policy.blog).to eq artificial_blog
end
it "returns an instantiated policy given a plain model class" do
policy = Pundit.policy(user, ArtificialBlog)
expect(policy.user).to eq user
expect(policy.blog).to eq ArtificialBlog
end
it "returns an instantiated policy given a plain model instance providing an anonymous class" do
policy = Pundit.policy(user, article_tag)
expect(policy.user).to eq user
expect(policy.tag).to eq article_tag
end
it "returns an instantiated policy given a plain model class providing an anonymous class" do
policy = Pundit.policy(user, ArticleTag)
expect(policy.user).to eq user
expect(policy.tag).to eq ArticleTag
end
end
end
describe ".policy!" do
it "returns an instantiated policy given a plain model instance" do
policy = Pundit.policy!(user, post)
expect(policy.user).to eq user
expect(policy.post).to eq post
end
it "returns an instantiated policy given an active model instance" do
policy = Pundit.policy!(user, comment)
expect(policy.user).to eq user
expect(policy.comment).to eq comment
end
it "returns an instantiated policy given a plain model class" do
policy = Pundit.policy!(user, Post)
expect(policy.user).to eq user
expect(policy.post).to eq Post
end
it "returns an instantiated policy given an active model class" do
policy = Pundit.policy!(user, Comment)
expect(policy.user).to eq user
expect(policy.comment).to eq Comment
end
it "returns an instantiated policy given a symbol" do
policy = Pundit.policy!(user, :criteria)
expect(policy.class).to eq CriteriaPolicy
expect(policy.user).to eq user
expect(policy.criteria).to eq :criteria
end
it "returns an instantiated policy given an array of symbols" do
policy = Pundit.policy!(user, %i[project criteria])
expect(policy.class).to eq Project::CriteriaPolicy
expect(policy.user).to eq user
expect(policy.criteria).to eq :criteria
end
it "throws an exception if the given policy can't be found" do
expect { Pundit.policy!(user, article) }.to raise_error(Pundit::NotDefinedError)
expect { Pundit.policy!(user, Article) }.to raise_error(Pundit::NotDefinedError)
end
it "returns the specified NilClassPolicy for nil" do
expect(Pundit.policy!(user, nil)).to be_a NilClassPolicy
end
it "raises an error with a invalid policy constructor" do
expect do
Pundit.policy(user, Wiki)
end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy> constructor is called")
end
end
describe ".included" do
it "includes Authorization module" do
klass = Class.new
expect do
klass.include Pundit
end.to output.to_stderr
expect(klass).to include Pundit::Authorization
end
it "warns about deprecation" do
klass = Class.new
expect do
klass.include Pundit
end.to output(a_string_starting_with("'include Pundit' is deprecated")).to_stderr
end
end
describe "Pundit::NotAuthorizedError" do
it "can be initialized with a string as message" do
error = Pundit::NotAuthorizedError.new("must be logged in")
expect(error.message).to eq "must be logged in"
end
end
end
================================================
FILE: spec/rspec_dsl_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
RSpec.describe "Pundit RSpec DSL", type: :policy do
let(:fake_rspec) do
double = class_double(RSpec::Core::ExampleGroup)
double.extend(::Pundit::RSpec::DSL)
double
end
let(:block) { proc { "block content" } }
let(:user) { double }
let(:other_user) { double }
let(:post) { Post.new(user) }
let(:policy) { PostPolicy }
it "calls describe with the correct metadata and without :focus" do
expected_metadata = {permissions: %i[item1 item2], caller: instance_of(Array)}
expect(fake_rspec).to receive(:describe).with("item1 and item2", match(expected_metadata)) do |&block|
expect(block.call).to eq("block content")
end
fake_rspec.permissions(:item1, :item2, &block)
end
it "calls describe with the correct metadata and with :focus" do
expected_metadata = {permissions: %i[item1 item2], caller: instance_of(Array), focus: true}
expect(fake_rspec).to receive(:describe).with("item1 and item2", match(expected_metadata)) do |&block|
expect(block.call).to eq("block content")
end
fake_rspec.permissions(:item1, :item2, :focus, &block)
end
describe "#permit" do
context "when not appropriately wrapped in permissions" do
it "raises a descriptive error" do
expect do
expect(policy).to permit(user, post)
end.to raise_error(KeyError, <<~MSG.strip)
No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`?
MSG
end
end
permissions :edit?, :update? do
it "succeeds when action is permitted" do
expect(policy).to permit(user, post)
end
context "when it fails" do
it "fails with a descriptive error message" do
expect do
expect(policy).to permit(other_user, post)
end.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip)
Expected PostPolicy to grant edit? and update? on Post but edit? and update? were not granted
MSG
end
end
context "when negated" do
it "succeeds when action is not permitted" do
expect(policy).not_to permit(other_user, post)
end
context "when it fails" do
it "fails with a descriptive error message" do
expect do
expect(policy).not_to permit(user, post)
end.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip)
Expected PostPolicy not to grant edit? and update? on Post but edit? and update? were granted
MSG
end
end
end
end
end
end
================================================
FILE: spec/spec_helper.rb
================================================
# frozen_string_literal: true
if ENV["COVERAGE"]
require "simplecov"
require "simplecov_json_formatter"
SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
SimpleCov::Formatter::HTMLFormatter,
SimpleCov::Formatter::JSONFormatter
])
SimpleCov.start do
enable_coverage :branch
primary_coverage :branch
end
SimpleCov.minimum_coverage_by_file line: 100, branch: 100
end
# @see https://github.com/rails/rails/issues/54260
require "logger" if RUBY_ENGINE == "jruby" && RUBY_ENGINE_VERSION.start_with?("9.3")
require "pundit"
require "pundit/rspec"
require "active_model/naming"
# Load all supporting files: models, policies, etc.
require "zeitwerk"
loader = Zeitwerk::Loader.new
loader.push_dir(File.expand_path("support/models", __dir__))
loader.push_dir(File.expand_path("support/policies", __dir__))
loader.push_dir(File.expand_path("support/lib", __dir__))
loader.setup
loader.eager_load
================================================
FILE: spec/support/lib/controller.rb
================================================
# frozen_string_literal: true
class Controller
attr_accessor :current_user
attr_reader :action_name, :params
class View
def initialize(controller)
@controller = controller
end
attr_reader :controller
end
class << self
def helper(mod)
View.include(mod)
end
def helper_method(method)
View.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args, **kwargs, &block)
controller.send(:#{method}, *args, **kwargs, &block)
end
RUBY
end
end
include Pundit::Authorization
# Mark protected methods public so they may be called in test
public(*Pundit::Authorization.protected_instance_methods)
def initialize(current_user, action_name, params)
@current_user = current_user
@action_name = action_name
@params = params
end
end
================================================
FILE: spec/support/lib/custom_cache.rb
================================================
# frozen_string_literal: true
class CustomCache
def initialize
@store = {}
end
def to_h
@store
end
def [](key)
@store[key]
end
def []=(key, value)
@store[key] = value
end
end
================================================
FILE: spec/support/lib/instance_tracking.rb
================================================
# frozen_string_literal: true
module InstanceTracking
module ClassMethods
def instances
@instances || 0
end
attr_writer :instances
end
def self.prepended(other)
other.extend(ClassMethods)
end
def initialize(*args, **kwargs, &block)
self.class.instances += 1
super
end
end
================================================
FILE: spec/support/models/article.rb
================================================
# frozen_string_literal: true
class Article
end
================================================
FILE: spec/support/models/article_tag.rb
================================================
# frozen_string_literal: true
class ArticleTag
def self.policy_class
ArticleTagOtherNamePolicy
end
end
================================================
FILE: spec/support/models/artificial_blog.rb
================================================
# frozen_string_literal: true
class ArtificialBlog < Blog
def self.policy_class
BlogPolicy
end
end
================================================
FILE: spec/support/models/blog.rb
================================================
# frozen_string_literal: true
class Blog
end
================================================
FILE: spec/support/models/comment.rb
================================================
# frozen_string_literal: true
class Comment
extend ActiveModel::Naming
end
================================================
FILE: spec/support/models/comment_four_five_six.rb
================================================
# frozen_string_literal: true
class CommentFourFiveSix
extend ActiveModel::Naming
end
================================================
FILE: spec/support/models/comment_scope.rb
================================================
# frozen_string_literal: true
class CommentScope
attr_reader :original_object
def initialize(original_object)
@original_object = original_object
end
def ==(other)
original_object == other.original_object
end
end
================================================
FILE: spec/support/models/comments_relation.rb
================================================
# frozen_string_literal: true
class CommentsRelation
def initialize(empty: false)
@empty = empty
end
def self.model_name
Comment.model_name
end
end
================================================
FILE: spec/support/models/customer/post.rb
================================================
# frozen_string_literal: true
module Customer
class Post < ::Post
extend ActiveModel::Naming
def self.policy_class
PostPolicy
end
end
end
================================================
FILE: spec/support/models/default_scope_contains_error.rb
================================================
# frozen_string_literal: true
class DefaultScopeContainsError
def self.all
end
end
================================================
FILE: spec/support/models/dummy_current_user.rb
================================================
# frozen_string_literal: true
class DummyCurrentUser
end
================================================
FILE: spec/support/models/foo.rb
================================================
# frozen_string_literal: true
class Foo
end
================================================
FILE: spec/support/models/post.rb
================================================
# frozen_string_literal: true
class Post
def initialize(user = nil)
@user = user
end
attr_reader :user
def self.published
:published
end
def self.read
:read
end
def to_s
"Post"
end
end
================================================
FILE: spec/support/models/post_four_five_six.rb
================================================
# frozen_string_literal: true
class PostFourFiveSix
def initialize(user)
@user = user
end
attr_reader(:user)
end
================================================
FILE: spec/support/models/project_one_two_three/avatar_four_five_six.rb
================================================
# frozen_string_literal: true
module ProjectOneTwoThree
class AvatarFourFiveSix
extend ActiveModel::Naming
end
end
================================================
FILE: spec/support/models/project_one_two_three/tag_four_five_six.rb
================================================
# frozen_string_literal: true
module ProjectOneTwoThree
class TagFourFiveSix
def initialize(user)
@user = user
end
attr_reader(:user)
end
end
================================================
FILE: spec/support/models/wiki.rb
================================================
# frozen_string_literal: true
class Wiki
end
================================================
FILE: spec/support/policies/article_tag_other_name_policy.rb
================================================
# frozen_string_literal: true
class ArticleTagOtherNamePolicy < BasePolicy
def show?
true
end
def destroy?
false
end
alias_method :tag, :record
end
================================================
FILE: spec/support/policies/base_policy.rb
================================================
# frozen_string_literal: true
class BasePolicy
prepend InstanceTracking
class BaseScope
prepend InstanceTracking
def initialize(user, scope)
@user = user
@scope = scope
end
attr_reader :user, :scope
end
def initialize(user, record)
@user = user
@record = record
end
attr_reader :user, :record
end
================================================
FILE: spec/support/policies/blog_policy.rb
================================================
# frozen_string_literal: true
class BlogPolicy < BasePolicy
alias_method :blog, :record
end
================================================
FILE: spec/support/policies/comment_policy.rb
================================================
# frozen_string_literal: true
class CommentPolicy < BasePolicy
class Scope < BaseScope
def resolve
CommentScope.new(scope)
end
end
alias_method :comment, :record
end
================================================
FILE: spec/support/policies/criteria_policy.rb
================================================
# frozen_string_literal: true
class CriteriaPolicy < BasePolicy
alias_method :criteria, :record
end
================================================
FILE: spec/support/policies/default_scope_contains_error_policy.rb
================================================
# frozen_string_literal: true
class DefaultScopeContainsErrorPolicy < BasePolicy
class Scope < BaseScope
def resolve
# deliberate wrong usage of the method
raise "This is an arbitrary error that should bubble up"
end
end
end
================================================
FILE: spec/support/policies/dummy_current_user_policy.rb
================================================
# frozen_string_literal: true
class DummyCurrentUserPolicy < BasePolicy
class Scope < BasePolicy::BaseScope
def resolve
user
end
end
end
================================================
FILE: spec/support/policies/nil_class_policy.rb
================================================
# frozen_string_literal: true
class NilClassPolicy < BasePolicy
class Scope
def initialize(*)
raise Pundit::NotDefinedError, "Cannot scope NilClass"
end
end
def destroy?
false
end
end
================================================
FILE: spec/support/policies/post_policy.rb
================================================
# frozen_string_literal: true
class PostPolicy < BasePolicy
class Scope < BaseScope
def resolve
scope.published
end
end
alias_method :post, :record
def update?
post.user == user
end
alias_method :edit?, :update?
def destroy?
false
end
def show?
true
end
def permitted_attributes
if post.user == user
%i[title votes]
else
[:votes]
end
end
def permitted_attributes_for_revise
[:body]
end
def expected_attributes_for_action(action_name)
case action_name.to_sym
when :revise
[:body]
else
if post.user == user
%i[title votes]
else
[:votes]
end
end
end
end
================================================
FILE: spec/support/policies/project/admin/comment_policy.rb
================================================
# frozen_string_literal: true
module Project
module Admin
class CommentPolicy < BasePolicy
def update?
true
end
def destroy?
false
end
end
end
end
================================================
FILE: spec/support/policies/project/comment_policy.rb
================================================
# frozen_string_literal: true
module Project
class CommentPolicy < BasePolicy
class Scope < BaseScope
def resolve
scope
end
end
def update?
true
end
alias_method :comment, :record
end
end
================================================
FILE: spec/support/policies/project/criteria_policy.rb
================================================
# frozen_string_literal: true
module Project
class CriteriaPolicy < BasePolicy
alias_method :criteria, :record
end
end
================================================
FILE: spec/support/policies/project/post_policy.rb
================================================
# frozen_string_literal: true
module Project
class PostPolicy < BasePolicy
class Scope < BaseScope
def resolve
scope.read
end
end
alias_method :post, :record
end
end
================================================
FILE: spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb
================================================
# frozen_string_literal: true
module ProjectOneTwoThree
class AvatarFourFiveSixPolicy < BasePolicy
end
end
================================================
FILE: spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb
================================================
# frozen_string_literal: true
module ProjectOneTwoThree
class CommentFourFiveSixPolicy < BasePolicy
end
end
================================================
FILE: spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb
================================================
# frozen_string_literal: true
module ProjectOneTwoThree
class CriteriaFourFiveSixPolicy < BasePolicy
end
end
================================================
FILE: spec/support/policies/project_one_two_three/post_four_five_six_policy.rb
================================================
# frozen_string_literal: true
module ProjectOneTwoThree
class PostFourFiveSixPolicy < BasePolicy
end
end
================================================
FILE: spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb
================================================
# frozen_string_literal: true
module ProjectOneTwoThree
class TagFourFiveSixPolicy < BasePolicy
end
end
================================================
FILE: spec/support/policies/publication_policy.rb
================================================
# frozen_string_literal: true
class PublicationPolicy < BasePolicy
class Scope < BaseScope
def resolve
scope.published
end
end
def create?
true
end
end
================================================
FILE: spec/support/policies/wiki_policy.rb
================================================
# frozen_string_literal: true
class WikiPolicy
class Scope
# deliberate typo method
def initalize
end
end
end
gitextract_wfsck2t6/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE/
│ │ └── gem_release_template.md
│ ├── pull_request_template.md
│ └── workflows/
│ ├── main.yml
│ └── push_gem.yml
├── .gitignore
├── .rubocop_ignore_git.yml
├── .standard.yml
├── .yardopts
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── SECURITY.md
├── bin/
│ ├── console
│ └── setup
├── config/
│ └── rubocop-rspec.yml
├── lib/
│ ├── generators/
│ │ ├── pundit/
│ │ │ ├── install/
│ │ │ │ ├── USAGE
│ │ │ │ ├── install_generator.rb
│ │ │ │ └── templates/
│ │ │ │ └── application_policy.rb.tt
│ │ │ └── policy/
│ │ │ ├── USAGE
│ │ │ ├── policy_generator.rb
│ │ │ └── templates/
│ │ │ └── policy.rb.tt
│ │ ├── rspec/
│ │ │ ├── policy_generator.rb
│ │ │ └── templates/
│ │ │ └── policy_spec.rb.tt
│ │ └── test_unit/
│ │ ├── policy_generator.rb
│ │ └── templates/
│ │ └── policy_test.rb.tt
│ ├── pundit/
│ │ ├── authorization.rb
│ │ ├── cache_store/
│ │ │ ├── legacy_store.rb
│ │ │ └── null_store.rb
│ │ ├── cache_store.rb
│ │ ├── context.rb
│ │ ├── error.rb
│ │ ├── helper.rb
│ │ ├── policy_finder.rb
│ │ ├── railtie.rb
│ │ ├── rspec.rb
│ │ └── version.rb
│ └── pundit.rb
├── pundit.gemspec
└── spec/
├── authorization_spec.rb
├── generators_spec.rb
├── policies/
│ └── post_policy_spec.rb
├── policy_finder_spec.rb
├── pundit/
│ └── helper_spec.rb
├── pundit_spec.rb
├── rspec_dsl_spec.rb
├── spec_helper.rb
└── support/
├── lib/
│ ├── controller.rb
│ ├── custom_cache.rb
│ └── instance_tracking.rb
├── models/
│ ├── article.rb
│ ├── article_tag.rb
│ ├── artificial_blog.rb
│ ├── blog.rb
│ ├── comment.rb
│ ├── comment_four_five_six.rb
│ ├── comment_scope.rb
│ ├── comments_relation.rb
│ ├── customer/
│ │ └── post.rb
│ ├── default_scope_contains_error.rb
│ ├── dummy_current_user.rb
│ ├── foo.rb
│ ├── post.rb
│ ├── post_four_five_six.rb
│ ├── project_one_two_three/
│ │ ├── avatar_four_five_six.rb
│ │ └── tag_four_five_six.rb
│ └── wiki.rb
└── policies/
├── article_tag_other_name_policy.rb
├── base_policy.rb
├── blog_policy.rb
├── comment_policy.rb
├── criteria_policy.rb
├── default_scope_contains_error_policy.rb
├── dummy_current_user_policy.rb
├── nil_class_policy.rb
├── post_policy.rb
├── project/
│ ├── admin/
│ │ └── comment_policy.rb
│ ├── comment_policy.rb
│ ├── criteria_policy.rb
│ └── post_policy.rb
├── project_one_two_three/
│ ├── avatar_four_five_six_policy.rb
│ ├── comment_four_five_six_policy.rb
│ ├── criteria_four_five_six_policy.rb
│ ├── post_four_five_six_policy.rb
│ └── tag_four_five_six_policy.rb
├── publication_policy.rb
└── wiki_policy.rb
SYMBOL INDEX (218 symbols across 57 files)
FILE: lib/generators/pundit/install/install_generator.rb
type Pundit (line 3) | module Pundit
type Generators (line 5) | module Generators
class InstallGenerator (line 7) | class InstallGenerator < ::Rails::Generators::Base
method copy_application_policy (line 10) | def copy_application_policy
FILE: lib/generators/pundit/policy/policy_generator.rb
type Pundit (line 3) | module Pundit
type Generators (line 5) | module Generators
class PolicyGenerator (line 7) | class PolicyGenerator < ::Rails::Generators::NamedBase
method create_policy (line 10) | def create_policy
FILE: lib/generators/rspec/policy_generator.rb
type Rspec (line 4) | module Rspec
type Generators (line 6) | module Generators
class PolicyGenerator (line 8) | class PolicyGenerator < ::Rails::Generators::NamedBase
method create_policy_spec (line 11) | def create_policy_spec
FILE: lib/generators/test_unit/policy_generator.rb
type TestUnit (line 4) | module TestUnit
type Generators (line 6) | module Generators
class PolicyGenerator (line 8) | class PolicyGenerator < ::Rails::Generators::NamedBase
method create_policy_test (line 11) | def create_policy_test
FILE: lib/pundit.rb
type Pundit (line 22) | module Pundit
type Generators (line 31) | module Generators; end
function included (line 33) | def self.included(base)
function authorize (line 45) | def authorize(user, record, query, policy_class: nil, cache: nil)
function policy_scope (line 58) | def policy_scope(user, *args, **kwargs, &block)
function policy_scope! (line 64) | def policy_scope!(user, *args, **kwargs, &block)
function policy (line 70) | def policy(user, *args, **kwargs, &block)
function policy! (line 76) | def policy!(user, *args, **kwargs, &block)
FILE: lib/pundit/authorization.rb
type Pundit (line 3) | module Pundit
type Authorization (line 13) | module Authorization
function pundit (line 35) | def pundit
function pundit_user (line 51) | def pundit_user
function pundit_reset! (line 66) | def pundit_reset!
function authorize (line 89) | def authorize(record, query = nil, policy_class: nil)
function skip_authorization (line 103) | def skip_authorization
function pundit_policy_authorized? (line 111) | def pundit_policy_authorized?
function verify_authorized (line 126) | def verify_authorized
function policies (line 136) | def policies
function policy (line 150) | def policy(record)
function policy_scope (line 163) | def policy_scope(scope, policy_scope_class: nil)
function skip_policy_scope (line 174) | def skip_policy_scope
function pundit_policy_scoped? (line 182) | def pundit_policy_scoped?
function verify_policy_scoped (line 198) | def verify_policy_scoped
function policy_scopes (line 208) | def policy_scopes
function pundit_policy_scope (line 225) | def pundit_policy_scope(scope)
function expected_attributes (line 251) | def expected_attributes(record, action: action_name, param_key: pund...
function pundit_param_key (line 260) | def pundit_param_key(record)
function permitted_attributes (line 278) | def permitted_attributes(record, action = action_name)
function pundit_params_for (line 293) | def pundit_params_for(record)
FILE: lib/pundit/cache_store.rb
type Pundit (line 3) | module Pundit
type CacheStore (line 9) | module CacheStore
FILE: lib/pundit/cache_store/legacy_store.rb
type Pundit (line 3) | module Pundit
type CacheStore (line 4) | module CacheStore
class LegacyStore (line 11) | class LegacyStore
method initialize (line 13) | def initialize(hash = {})
method fetch (line 21) | def fetch(user:, record:)
FILE: lib/pundit/cache_store/null_store.rb
type Pundit (line 3) | module Pundit
type CacheStore (line 4) | module CacheStore
class NullStore (line 12) | class NullStore
method fetch (line 25) | def fetch(*, **)
FILE: lib/pundit/context.rb
type Pundit (line 3) | module Pundit
class Context (line 30) | class Context
method initialize (line 35) | def initialize(user:, policy_cache: CacheStore::NullStore.instance)
method authorize (line 62) | def authorize(possibly_namespaced_record, query:, policy_class:)
method policy (line 82) | def policy(record)
method policy! (line 94) | def policy!(record)
method policy_scope (line 109) | def policy_scope(scope)
method policy_scope! (line 130) | def policy_scope!(scope)
method cached_find (line 158) | def cached_find(record)
method policy_finder (line 178) | def policy_finder(record)
method pundit_model (line 186) | def pundit_model(record)
FILE: lib/pundit/error.rb
type Pundit (line 3) | module Pundit
class Error (line 8) | class Error < StandardError; end
class NotAuthorizedError (line 12) | class NotAuthorizedError < Error
method initialize (line 36) | def initialize(options = {})
class InvalidConstructorError (line 56) | class InvalidConstructorError < Error; end
class AuthorizationNotPerformedError (line 61) | class AuthorizationNotPerformedError < Error; end
class PolicyScopingNotPerformedError (line 66) | class PolicyScopingNotPerformedError < AuthorizationNotPerformedError;...
class NotDefinedError (line 70) | class NotDefinedError < Error; end
FILE: lib/pundit/helper.rb
type Pundit (line 3) | module Pundit
type Helper (line 9) | module Helper
function policy_scope (line 12) | def policy_scope(scope)
FILE: lib/pundit/policy_finder.rb
type Pundit (line 6) | module Pundit
class PolicyFinder (line 16) | class PolicyFinder
method initialize (line 29) | def initialize(object)
method scope (line 40) | def scope
method policy (line 52) | def policy
method scope! (line 61) | def scope!
method policy! (line 69) | def policy!
method param_key (line 76) | def param_key # rubocop:disable Metrics/AbcSize
method find (line 96) | def find(subject)
method find_class_name (line 121) | def find_class_name(subject)
FILE: lib/pundit/railtie.rb
type Pundit (line 3) | module Pundit
class Railtie (line 5) | class Railtie < Rails::Railtie
FILE: lib/pundit/rspec.rb
type Pundit (line 7) | module Pundit
type RSpec (line 10) | module RSpec
type Matchers (line 12) | module Matchers
function description (line 29) | def description(user, record)
function was_or_were (line 62) | def was_or_were
function current_example (line 90) | def current_example
function current_example (line 96) | def current_example
function permissions (line 102) | def permissions
type DSL (line 114) | module DSL
function permissions (line 131) | def permissions(*list, &block)
type PolicyExampleGroup (line 147) | module PolicyExampleGroup
function included (line 150) | def self.included(base)
FILE: lib/pundit/version.rb
type Pundit (line 3) | module Pundit
FILE: spec/authorization_spec.rb
function to_params (line 7) | def to_params(*args, **kwargs, &block)
FILE: spec/support/lib/controller.rb
class Controller (line 3) | class Controller
class View (line 7) | class View
method initialize (line 8) | def initialize(controller)
method helper (line 16) | def helper(mod)
method helper_method (line 20) | def helper_method(method)
method initialize (line 34) | def initialize(current_user, action_name, params)
FILE: spec/support/lib/custom_cache.rb
class CustomCache (line 3) | class CustomCache
method initialize (line 4) | def initialize
method to_h (line 8) | def to_h
method [] (line 12) | def [](key)
method []= (line 16) | def []=(key, value)
FILE: spec/support/lib/instance_tracking.rb
type InstanceTracking (line 3) | module InstanceTracking
type ClassMethods (line 4) | module ClassMethods
function instances (line 5) | def instances
function prepended (line 12) | def self.prepended(other)
function initialize (line 16) | def initialize(*args, **kwargs, &block)
FILE: spec/support/models/article.rb
class Article (line 3) | class Article
FILE: spec/support/models/article_tag.rb
class ArticleTag (line 3) | class ArticleTag
method policy_class (line 4) | def self.policy_class
FILE: spec/support/models/artificial_blog.rb
class ArtificialBlog (line 3) | class ArtificialBlog < Blog
method policy_class (line 4) | def self.policy_class
FILE: spec/support/models/blog.rb
class Blog (line 3) | class Blog
FILE: spec/support/models/comment.rb
class Comment (line 3) | class Comment
FILE: spec/support/models/comment_four_five_six.rb
class CommentFourFiveSix (line 3) | class CommentFourFiveSix
FILE: spec/support/models/comment_scope.rb
class CommentScope (line 3) | class CommentScope
method initialize (line 6) | def initialize(original_object)
method == (line 10) | def ==(other)
FILE: spec/support/models/comments_relation.rb
class CommentsRelation (line 3) | class CommentsRelation
method initialize (line 4) | def initialize(empty: false)
method model_name (line 8) | def self.model_name
FILE: spec/support/models/customer/post.rb
type Customer (line 3) | module Customer
class Post (line 4) | class Post < ::Post
method policy_class (line 7) | def self.policy_class
FILE: spec/support/models/default_scope_contains_error.rb
class DefaultScopeContainsError (line 3) | class DefaultScopeContainsError
method all (line 4) | def self.all
FILE: spec/support/models/dummy_current_user.rb
class DummyCurrentUser (line 3) | class DummyCurrentUser
FILE: spec/support/models/foo.rb
class Foo (line 3) | class Foo
FILE: spec/support/models/post.rb
class Post (line 3) | class Post
method initialize (line 4) | def initialize(user = nil)
method published (line 10) | def self.published
method read (line 14) | def self.read
method to_s (line 18) | def to_s
FILE: spec/support/models/post_four_five_six.rb
class PostFourFiveSix (line 3) | class PostFourFiveSix
method initialize (line 4) | def initialize(user)
FILE: spec/support/models/project_one_two_three/avatar_four_five_six.rb
type ProjectOneTwoThree (line 3) | module ProjectOneTwoThree
class AvatarFourFiveSix (line 4) | class AvatarFourFiveSix
FILE: spec/support/models/project_one_two_three/tag_four_five_six.rb
type ProjectOneTwoThree (line 3) | module ProjectOneTwoThree
class TagFourFiveSix (line 4) | class TagFourFiveSix
method initialize (line 5) | def initialize(user)
FILE: spec/support/models/wiki.rb
class Wiki (line 3) | class Wiki
FILE: spec/support/policies/article_tag_other_name_policy.rb
class ArticleTagOtherNamePolicy (line 3) | class ArticleTagOtherNamePolicy < BasePolicy
method show? (line 4) | def show?
method destroy? (line 8) | def destroy?
FILE: spec/support/policies/base_policy.rb
class BasePolicy (line 3) | class BasePolicy
class BaseScope (line 6) | class BaseScope
method initialize (line 9) | def initialize(user, scope)
method initialize (line 17) | def initialize(user, record)
FILE: spec/support/policies/blog_policy.rb
class BlogPolicy (line 3) | class BlogPolicy < BasePolicy
FILE: spec/support/policies/comment_policy.rb
class CommentPolicy (line 3) | class CommentPolicy < BasePolicy
class Scope (line 4) | class Scope < BaseScope
method resolve (line 5) | def resolve
FILE: spec/support/policies/criteria_policy.rb
class CriteriaPolicy (line 3) | class CriteriaPolicy < BasePolicy
FILE: spec/support/policies/default_scope_contains_error_policy.rb
class DefaultScopeContainsErrorPolicy (line 3) | class DefaultScopeContainsErrorPolicy < BasePolicy
class Scope (line 4) | class Scope < BaseScope
method resolve (line 5) | def resolve
FILE: spec/support/policies/dummy_current_user_policy.rb
class DummyCurrentUserPolicy (line 3) | class DummyCurrentUserPolicy < BasePolicy
class Scope (line 4) | class Scope < BasePolicy::BaseScope
method resolve (line 5) | def resolve
FILE: spec/support/policies/nil_class_policy.rb
class NilClassPolicy (line 3) | class NilClassPolicy < BasePolicy
class Scope (line 4) | class Scope
method initialize (line 5) | def initialize(*)
method destroy? (line 10) | def destroy?
FILE: spec/support/policies/post_policy.rb
class PostPolicy (line 3) | class PostPolicy < BasePolicy
class Scope (line 4) | class Scope < BaseScope
method resolve (line 5) | def resolve
method update? (line 12) | def update?
method destroy? (line 17) | def destroy?
method show? (line 21) | def show?
method permitted_attributes (line 25) | def permitted_attributes
method permitted_attributes_for_revise (line 33) | def permitted_attributes_for_revise
method expected_attributes_for_action (line 37) | def expected_attributes_for_action(action_name)
FILE: spec/support/policies/project/admin/comment_policy.rb
type Project (line 3) | module Project
type Admin (line 4) | module Admin
class CommentPolicy (line 5) | class CommentPolicy < BasePolicy
method update? (line 6) | def update?
method destroy? (line 10) | def destroy?
FILE: spec/support/policies/project/comment_policy.rb
type Project (line 3) | module Project
class CommentPolicy (line 4) | class CommentPolicy < BasePolicy
class Scope (line 5) | class Scope < BaseScope
method resolve (line 6) | def resolve
method update? (line 11) | def update?
FILE: spec/support/policies/project/criteria_policy.rb
type Project (line 3) | module Project
class CriteriaPolicy (line 4) | class CriteriaPolicy < BasePolicy
FILE: spec/support/policies/project/post_policy.rb
type Project (line 3) | module Project
class PostPolicy (line 4) | class PostPolicy < BasePolicy
class Scope (line 5) | class Scope < BaseScope
method resolve (line 6) | def resolve
FILE: spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb
type ProjectOneTwoThree (line 3) | module ProjectOneTwoThree
class AvatarFourFiveSixPolicy (line 4) | class AvatarFourFiveSixPolicy < BasePolicy
FILE: spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb
type ProjectOneTwoThree (line 3) | module ProjectOneTwoThree
class CommentFourFiveSixPolicy (line 4) | class CommentFourFiveSixPolicy < BasePolicy
FILE: spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb
type ProjectOneTwoThree (line 3) | module ProjectOneTwoThree
class CriteriaFourFiveSixPolicy (line 4) | class CriteriaFourFiveSixPolicy < BasePolicy
FILE: spec/support/policies/project_one_two_three/post_four_five_six_policy.rb
type ProjectOneTwoThree (line 3) | module ProjectOneTwoThree
class PostFourFiveSixPolicy (line 4) | class PostFourFiveSixPolicy < BasePolicy
FILE: spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb
type ProjectOneTwoThree (line 3) | module ProjectOneTwoThree
class TagFourFiveSixPolicy (line 4) | class TagFourFiveSixPolicy < BasePolicy
FILE: spec/support/policies/publication_policy.rb
class PublicationPolicy (line 3) | class PublicationPolicy < BasePolicy
class Scope (line 4) | class Scope < BaseScope
method resolve (line 5) | def resolve
method create? (line 10) | def create?
FILE: spec/support/policies/wiki_policy.rb
class WikiPolicy (line 3) | class WikiPolicy
class Scope (line 4) | class Scope
method initalize (line 6) | def initalize
Condensed preview — 92 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (147K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 411,
"preview": "---\nname: Bug report\nabout: Create a bug report to report a problem\ntitle: ''\nlabels: problem\nassignees: ''\n\n---\n\n**Desc"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 1019,
"preview": "---\nname: Feature request\nabout: Suggest an idea\ntitle: ''\nlabels: ['feature request']\nassignees: ''\n---\n\n**Please consi"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE/gem_release_template.md",
"chars": 382,
"preview": "## To do\n\n- [ ] Make changes:\n - [ ] Bump `Pundit::VERSION` in `lib/pundit/version.rb`.\n - [ ] Update `CHANGELOG.md`.\n"
},
{
"path": ".github/pull_request_template.md",
"chars": 344,
"preview": "## To do\n\n- [ ] I have read the [contributing guidelines](https://github.com/varvet/pundit/contribute).\n- [ ] I have add"
},
{
"path": ".github/workflows/main.yml",
"chars": 2429,
"preview": "name: Main\n\non:\n push:\n branches: [\"main\"]\n pull_request:\n workflow_dispatch:\n\npermissions:\n contents: read\n\njobs"
},
{
"path": ".github/workflows/push_gem.yml",
"chars": 786,
"preview": "name: Push Gem\n\non:\n workflow_dispatch:\n\npermissions:\n contents: read\n\njobs:\n push:\n if: github.repository == 'var"
},
{
"path": ".gitignore",
"chars": 164,
"preview": "*.gem\n*.rbc\n.bundle\n.config\n.coverage\n.yardoc\nGemfile.lock\nInstalledFiles\n_yardoc\ncoverage\ndoc/\nlib/bundler/man\npkg\nrdoc"
},
{
"path": ".rubocop_ignore_git.yml",
"chars": 278,
"preview": "# This is here so we can keep YAML syntax highlight in the main file.\nAllCops:\n Exclude:\n - \"lib/generators/**/templ"
},
{
"path": ".standard.yml",
"chars": 77,
"preview": "parallel: true\nruby_version: 3.2\n\nextend_config:\n - .rubocop_ignore_git.yml\n"
},
{
"path": ".yardopts",
"chars": 90,
"preview": "--no-private --private --protected --hide-void-return --markup markdown --fail-on-warning\n"
},
{
"path": "CHANGELOG.md",
"chars": 8658,
"preview": "# Pundit\n\n## Unreleased\n\n- Add support for `params.expect` using `expected_parameters` and `expected_parameters_for`. [#"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 1423,
"preview": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect all\npeople who cont"
},
{
"path": "CONTRIBUTING.md",
"chars": 1212,
"preview": "## Security issues\n\nIf you have found a security related issue, please do not file an issue on GitHub or send a PR addre"
},
{
"path": "Gemfile",
"chars": 538,
"preview": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\n\ngemspec\n\n# Rails-related - for testing purposes\ngem \"actio"
},
{
"path": "LICENSE.txt",
"chars": 1081,
"preview": "Copyright (c) 2019 Jonas Nicklas, Varvet AB\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person ob"
},
{
"path": "README.md",
"chars": 26384,
"preview": "# Pundit\n\n[](https://github.com/varvet/pun"
},
{
"path": "Rakefile",
"chars": 358,
"preview": "# frozen_string_literal: true\n\nrequire \"rubygems\"\nrequire \"bundler/gem_tasks\"\nrequire \"rspec/core/rake_task\"\nrequire \"ya"
},
{
"path": "SECURITY.md",
"chars": 456,
"preview": "# Security Policy\n\nPlease do not file an issue on GitHub, or send a PR addressing the issue.\n\n## Supported versions\n\nMos"
},
{
"path": "bin/console",
"chars": 280,
"preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire \"bundler/setup\"\nrequire \"pundit\"\n\n# You can add fixtures and/"
},
{
"path": "bin/setup",
"chars": 131,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\nIFS=$'\\n\\t'\nset -vx\n\nbundle install\n\n# Do any other automated setup that you need "
},
{
"path": "config/rubocop-rspec.yml",
"chars": 75,
"preview": "RSpec:\n Language:\n ExampleGroups:\n Regular:\n - permissions\n"
},
{
"path": "lib/generators/pundit/install/USAGE",
"chars": 91,
"preview": "Description:\n Generates an application policy as a starting point for your application.\n"
},
{
"path": "lib/generators/pundit/install/install_generator.rb",
"chars": 350,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # @private\n module Generators\n # @private\n class InstallGenerator "
},
{
"path": "lib/generators/pundit/install/templates/application_policy.rb.tt",
"chars": 619,
"preview": "# frozen_string_literal: true\n\nclass ApplicationPolicy\n attr_reader :user, :record\n\n def initialize(user, record)\n "
},
{
"path": "lib/generators/pundit/policy/USAGE",
"chars": 176,
"preview": "Description:\n Generates a policy for a model with the given name.\n\nExample:\n rails generate pundit:policy user\n\n "
},
{
"path": "lib/generators/pundit/policy/policy_generator.rb",
"chars": 391,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # @private\n module Generators\n # @private\n class PolicyGenerator <"
},
{
"path": "lib/generators/pundit/policy/templates/policy.rb.tt",
"chars": 587,
"preview": "<% module_namespacing do -%>\nclass <%= class_name %>Policy < ApplicationPolicy\n # NOTE: Up to Pundit v2.3.1, the inheri"
},
{
"path": "lib/generators/rspec/policy_generator.rb",
"chars": 385,
"preview": "# frozen_string_literal: true\n\n# @private\nmodule Rspec\n # @private\n module Generators\n # @private\n class PolicyG"
},
{
"path": "lib/generators/rspec/templates/policy_spec.rb.tt",
"chars": 664,
"preview": "require '<%= File.exist?('spec/rails_helper.rb') ? 'rails_helper' : 'spec_helper' %>'\n\nRSpec.describe <%= class_name %>P"
},
{
"path": "lib/generators/test_unit/policy_generator.rb",
"chars": 388,
"preview": "# frozen_string_literal: true\n\n# @private\nmodule TestUnit\n # @private\n module Generators\n # @private\n class Poli"
},
{
"path": "lib/generators/test_unit/templates/policy_test.rb.tt",
"chars": 209,
"preview": "require 'test_helper'\n\nclass <%= class_name %>PolicyTest < ActiveSupport::TestCase\n def test_scope\n end\n\n def test_sh"
},
{
"path": "lib/pundit/authorization.rb",
"chars": 10405,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # Pundit DSL to include in your controllers to provide authorization help"
},
{
"path": "lib/pundit/cache_store/legacy_store.rb",
"chars": 630,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n module CacheStore\n # A cache store that uses only the record as a cach"
},
{
"path": "lib/pundit/cache_store/null_store.rb",
"chars": 666,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n module CacheStore\n # A cache store that does not cache anything.\n #"
},
{
"path": "lib/pundit/cache_store.rb",
"chars": 745,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # Namespace for cache store implementations.\n #\n # Cache stores are use"
},
{
"path": "lib/pundit/context.rb",
"chars": 6181,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # {Pundit::Context} is intended to be created once per request and user, "
},
{
"path": "lib/pundit/error.rb",
"chars": 2424,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # @api private\n # @since v1.0.0\n # To avoid name clashes with common Er"
},
{
"path": "lib/pundit/helper.rb",
"chars": 382,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # Rails view helpers, to allow a slightly different view-specific\n # imp"
},
{
"path": "lib/pundit/policy_finder.rb",
"chars": 3855,
"preview": "# frozen_string_literal: true\n\n# String#safe_constantize, String#demodulize, String#underscore, String#camelize\nrequire "
},
{
"path": "lib/pundit/railtie.rb",
"chars": 565,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # @since v2.5.0\n class Railtie < Rails::Railtie\n if Rails.version.to_"
},
{
"path": "lib/pundit/rspec.rb",
"chars": 5018,
"preview": "# frozen_string_literal: true\n\nrequire \"pundit\"\n# Array#to_sentence\nrequire \"active_support/core_ext/array/conversions\"\n"
},
{
"path": "lib/pundit/version.rb",
"chars": 104,
"preview": "# frozen_string_literal: true\n\nmodule Pundit\n # The current version of Pundit.\n VERSION = \"2.5.2\"\nend\n"
},
{
"path": "lib/pundit.rb",
"chars": 2112,
"preview": "# frozen_string_literal: true\n\nrequire \"active_support\"\n\nrequire \"pundit/version\"\nrequire \"pundit/error\"\nrequire \"pundit"
},
{
"path": "pundit.gemspec",
"chars": 1174,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"lib/pundit/version\"\n\nGem::Specification.new do |gem|\n gem.name = \"pund"
},
{
"path": "spec/authorization_spec.rb",
"chars": 13115,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\nrequire \"action_controller\"\n\nRSpec.describe Pundit::Authorization d"
},
{
"path": "spec/generators_spec.rb",
"chars": 1209,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\nrequire \"tmpdir\"\n\nrequire \"rails/generators\"\nrequire \"generators/pu"
},
{
"path": "spec/policies/post_policy_spec.rb",
"chars": 1601,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\nRSpec.describe PostPolicy do\n let(:user) { double }\n let(:own_po"
},
{
"path": "spec/policy_finder_spec.rb",
"chars": 5301,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\nRSpec.describe Pundit::PolicyFinder do\n let(:user) { double }\n l"
},
{
"path": "spec/pundit/helper_spec.rb",
"chars": 458,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\nRSpec.describe Pundit::Helper do\n let(:user) { double }\n let(:co"
},
{
"path": "spec/pundit_spec.rb",
"chars": 18418,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\nRSpec.describe Pundit do\n let(:user) { double }\n let(:post) { Po"
},
{
"path": "spec/rspec_dsl_spec.rb",
"chars": 2670,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\nRSpec.describe \"Pundit RSpec DSL\", type: :policy do\n let(:fake_rs"
},
{
"path": "spec/spec_helper.rb",
"chars": 937,
"preview": "# frozen_string_literal: true\n\nif ENV[\"COVERAGE\"]\n require \"simplecov\"\n require \"simplecov_json_formatter\"\n\n SimpleCo"
},
{
"path": "spec/support/lib/controller.rb",
"chars": 840,
"preview": "# frozen_string_literal: true\n\nclass Controller\n attr_accessor :current_user\n attr_reader :action_name, :params\n\n cla"
},
{
"path": "spec/support/lib/custom_cache.rb",
"chars": 211,
"preview": "# frozen_string_literal: true\n\nclass CustomCache\n def initialize\n @store = {}\n end\n\n def to_h\n @store\n end\n\n "
},
{
"path": "spec/support/lib/instance_tracking.rb",
"chars": 318,
"preview": "# frozen_string_literal: true\n\nmodule InstanceTracking\n module ClassMethods\n def instances\n @instances || 0\n "
},
{
"path": "spec/support/models/article.rb",
"chars": 49,
"preview": "# frozen_string_literal: true\n\nclass Article\nend\n"
},
{
"path": "spec/support/models/article_tag.rb",
"chars": 112,
"preview": "# frozen_string_literal: true\n\nclass ArticleTag\n def self.policy_class\n ArticleTagOtherNamePolicy\n end\nend\n"
},
{
"path": "spec/support/models/artificial_blog.rb",
"chars": 108,
"preview": "# frozen_string_literal: true\n\nclass ArtificialBlog < Blog\n def self.policy_class\n BlogPolicy\n end\nend\n"
},
{
"path": "spec/support/models/blog.rb",
"chars": 46,
"preview": "# frozen_string_literal: true\n\nclass Blog\nend\n"
},
{
"path": "spec/support/models/comment.rb",
"chars": 78,
"preview": "# frozen_string_literal: true\n\nclass Comment\n extend ActiveModel::Naming\nend\n"
},
{
"path": "spec/support/models/comment_four_five_six.rb",
"chars": 89,
"preview": "# frozen_string_literal: true\n\nclass CommentFourFiveSix\n extend ActiveModel::Naming\nend\n"
},
{
"path": "spec/support/models/comment_scope.rb",
"chars": 233,
"preview": "# frozen_string_literal: true\n\nclass CommentScope\n attr_reader :original_object\n\n def initialize(original_object)\n "
},
{
"path": "spec/support/models/comments_relation.rb",
"chars": 166,
"preview": "# frozen_string_literal: true\n\nclass CommentsRelation\n def initialize(empty: false)\n @empty = empty\n end\n\n def sel"
},
{
"path": "spec/support/models/customer/post.rb",
"chars": 162,
"preview": "# frozen_string_literal: true\n\nmodule Customer\n class Post < ::Post\n extend ActiveModel::Naming\n\n def self.policy"
},
{
"path": "spec/support/models/default_scope_contains_error.rb",
"chars": 88,
"preview": "# frozen_string_literal: true\n\nclass DefaultScopeContainsError\n def self.all\n end\nend\n"
},
{
"path": "spec/support/models/dummy_current_user.rb",
"chars": 58,
"preview": "# frozen_string_literal: true\n\nclass DummyCurrentUser\nend\n"
},
{
"path": "spec/support/models/foo.rb",
"chars": 45,
"preview": "# frozen_string_literal: true\n\nclass Foo\nend\n"
},
{
"path": "spec/support/models/post.rb",
"chars": 224,
"preview": "# frozen_string_literal: true\n\nclass Post\n def initialize(user = nil)\n @user = user\n end\n\n attr_reader :user\n\n de"
},
{
"path": "spec/support/models/post_four_five_six.rb",
"chars": 125,
"preview": "# frozen_string_literal: true\n\nclass PostFourFiveSix\n def initialize(user)\n @user = user\n end\n\n attr_reader(:user)"
},
{
"path": "spec/support/models/project_one_two_three/avatar_four_five_six.rb",
"chars": 124,
"preview": "# frozen_string_literal: true\n\nmodule ProjectOneTwoThree\n class AvatarFourFiveSix\n extend ActiveModel::Naming\n end\n"
},
{
"path": "spec/support/models/project_one_two_three/tag_four_five_six.rb",
"chars": 166,
"preview": "# frozen_string_literal: true\n\nmodule ProjectOneTwoThree\n class TagFourFiveSix\n def initialize(user)\n @user = u"
},
{
"path": "spec/support/models/wiki.rb",
"chars": 46,
"preview": "# frozen_string_literal: true\n\nclass Wiki\nend\n"
},
{
"path": "spec/support/policies/article_tag_other_name_policy.rb",
"chars": 169,
"preview": "# frozen_string_literal: true\n\nclass ArticleTagOtherNamePolicy < BasePolicy\n def show?\n true\n end\n\n def destroy?\n "
},
{
"path": "spec/support/policies/base_policy.rb",
"chars": 351,
"preview": "# frozen_string_literal: true\n\nclass BasePolicy\n prepend InstanceTracking\n\n class BaseScope\n prepend InstanceTracki"
},
{
"path": "spec/support/policies/blog_policy.rb",
"chars": 95,
"preview": "# frozen_string_literal: true\n\nclass BlogPolicy < BasePolicy\n alias_method :blog, :record\nend\n"
},
{
"path": "spec/support/policies/comment_policy.rb",
"chars": 188,
"preview": "# frozen_string_literal: true\n\nclass CommentPolicy < BasePolicy\n class Scope < BaseScope\n def resolve\n CommentS"
},
{
"path": "spec/support/policies/criteria_policy.rb",
"chars": 103,
"preview": "# frozen_string_literal: true\n\nclass CriteriaPolicy < BasePolicy\n alias_method :criteria, :record\nend\n"
},
{
"path": "spec/support/policies/default_scope_contains_error_policy.rb",
"chars": 250,
"preview": "# frozen_string_literal: true\n\nclass DefaultScopeContainsErrorPolicy < BasePolicy\n class Scope < BaseScope\n def reso"
},
{
"path": "spec/support/policies/dummy_current_user_policy.rb",
"chars": 156,
"preview": "# frozen_string_literal: true\n\nclass DummyCurrentUserPolicy < BasePolicy\n class Scope < BasePolicy::BaseScope\n def r"
},
{
"path": "spec/support/policies/nil_class_policy.rb",
"chars": 212,
"preview": "# frozen_string_literal: true\n\nclass NilClassPolicy < BasePolicy\n class Scope\n def initialize(*)\n raise Pundit:"
},
{
"path": "spec/support/policies/post_policy.rb",
"chars": 701,
"preview": "# frozen_string_literal: true\n\nclass PostPolicy < BasePolicy\n class Scope < BaseScope\n def resolve\n scope.publi"
},
{
"path": "spec/support/policies/project/admin/comment_policy.rb",
"chars": 201,
"preview": "# frozen_string_literal: true\n\nmodule Project\n module Admin\n class CommentPolicy < BasePolicy\n def update?\n "
},
{
"path": "spec/support/policies/project/comment_policy.rb",
"chars": 241,
"preview": "# frozen_string_literal: true\n\nmodule Project\n class CommentPolicy < BasePolicy\n class Scope < BaseScope\n def r"
},
{
"path": "spec/support/policies/project/criteria_policy.rb",
"chars": 128,
"preview": "# frozen_string_literal: true\n\nmodule Project\n class CriteriaPolicy < BasePolicy\n alias_method :criteria, :record\n "
},
{
"path": "spec/support/policies/project/post_policy.rb",
"chars": 204,
"preview": "# frozen_string_literal: true\n\nmodule Project\n class PostPolicy < BasePolicy\n class Scope < BaseScope\n def reso"
},
{
"path": "spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb",
"chars": 112,
"preview": "# frozen_string_literal: true\n\nmodule ProjectOneTwoThree\n class AvatarFourFiveSixPolicy < BasePolicy\n end\nend\n"
},
{
"path": "spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb",
"chars": 113,
"preview": "# frozen_string_literal: true\n\nmodule ProjectOneTwoThree\n class CommentFourFiveSixPolicy < BasePolicy\n end\nend\n"
},
{
"path": "spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb",
"chars": 114,
"preview": "# frozen_string_literal: true\n\nmodule ProjectOneTwoThree\n class CriteriaFourFiveSixPolicy < BasePolicy\n end\nend\n"
},
{
"path": "spec/support/policies/project_one_two_three/post_four_five_six_policy.rb",
"chars": 110,
"preview": "# frozen_string_literal: true\n\nmodule ProjectOneTwoThree\n class PostFourFiveSixPolicy < BasePolicy\n end\nend\n"
},
{
"path": "spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb",
"chars": 109,
"preview": "# frozen_string_literal: true\n\nmodule ProjectOneTwoThree\n class TagFourFiveSixPolicy < BasePolicy\n end\nend\n"
},
{
"path": "spec/support/policies/publication_policy.rb",
"chars": 180,
"preview": "# frozen_string_literal: true\n\nclass PublicationPolicy < BasePolicy\n class Scope < BaseScope\n def resolve\n scop"
},
{
"path": "spec/support/policies/wiki_policy.rb",
"chars": 127,
"preview": "# frozen_string_literal: true\n\nclass WikiPolicy\n class Scope\n # deliberate typo method\n def initalize\n end\n e"
}
]
About this extraction
This page contains the full source code of the varvet/pundit GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 92 files (132.4 KB), approximately 37.0k tokens, and a symbol index with 218 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.