{"id":19558498,"url":"https://github.com/mach-kernel/warhol","last_synced_at":"2025-04-26T23:31:57.908Z","repository":{"id":146171600,"uuid":"91629672","full_name":"mach-kernel/warhol","owner":"mach-kernel","description":"A better way to do CanCanCan ability classes.","archived":false,"fork":false,"pushed_at":"2017-12-05T16:16:04.000Z","size":133,"stargazers_count":11,"open_issues_count":1,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-04T18:22:03.746Z","etag":null,"topics":["ability","acl","authorisation","authorization","authorization-middleware","cancan","cancancan","namespace","optimize","permissions","policy","rails","refactor","ruby","security","warhol"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mach-kernel.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-17T23:45:52.000Z","updated_at":"2021-10-01T12:26:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"2e90abc9-97a2-41b4-8bef-e23fd8eca118","html_url":"https://github.com/mach-kernel/warhol","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mach-kernel%2Fwarhol","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mach-kernel%2Fwarhol/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mach-kernel%2Fwarhol/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mach-kernel%2Fwarhol/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mach-kernel","download_url":"https://codeload.github.com/mach-kernel/warhol/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251068039,"owners_count":21531475,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ability","acl","authorisation","authorization","authorization-middleware","cancan","cancancan","namespace","optimize","permissions","policy","rails","refactor","ruby","security","warhol"],"created_at":"2024-11-11T04:47:18.731Z","updated_at":"2025-04-26T23:31:57.901Z","avatar_url":"https://github.com/mach-kernel.png","language":"Ruby","readme":"# warhol\n\n[![Code Climate](https://codeclimate.com/github/mach-kernel/warhol.png)](https://codeclimate.com/github/mach-kernel/warhol) ![CircleCI](https://circleci.com/gh/mach-kernel/warhol.svg?style=shield\u0026circle-token=00f71ed6911aab669bda9ff2432ca6c66d54a5e0) [![Test Coverage](https://codeclimate.com/github/mach-kernel/warhol/badges/coverage.svg)](https://codeclimate.com/github/mach-kernel/warhol/coverage)\n\nA better way to do [CanCanCan](https://github.com/CanCanCommunity/cancancan) [`Ability` classes](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities). Written in pure Ruby with `cancancan` as its only dependency. Designed to improve code quality in existing projects with CanCan.\n\n\u003cimg height=\"300px\" src=\"https://raw.github.com/mach-kernel/warhol/master/splash.jpg\" /\u003e\n\n## Getting Started\n\n### Motivations\n\nCanCan's official documentation says that this is what your ability class should look like:\n\n```ruby\nclass Ability\n  include CanCan::Ability\n\n  def initialize(user)\n    user ||= User.new # guest user (not logged in)\n    if user.admin?\n      can :manage, :all\n    else\n      can :read, :all\n    end\n  end\nend\n```\n\nFor small applications, this is simple and clear. However, in practice, some of these `Ability` classes are 200+ LoC monoliths that encompass the enforcement of many different kinds of permission sets without any clear separation of responsibility. Using `Warhol::Ability` allows you to have an individual set of permissions for each role in your domain.\n\n\n### Quick Start\n\nSpecify a method on your domain object (for many, an `ActiveRecord` or `Mongoid` model, but any PORO is fine) returning an array of strings, one for each role. Warhol then takes those strings and matches them up to your defined ability classes, applying the access permissions you defined there. Matching is performed on the names of the `Warhol::Ability` subclass, excluding their namespaces. Here is a database-backed example inspired by the above snippet from CanCan's official docs:\n\nFirst, some quick configuration. In a Rails project, we suggest this is placed in an initializer:\n\n#### Initializer\n\n```ruby\nrequire 'warhol'\n\nWarhol::Config.new do |warhol|\n  # This is the method we invoke below\n  warhol.role_accessor = :role_names\n\n  # Exposes a basic attr_reader. More below.\n  warhol.additional_accessors = ['user']\nend\n```\n\n#### The Domain Object\n\nThe domain object can look like this: \n\n```ruby\nclass User \u003c ActiveRecord::Base\n  has_and_belongs_to_many :roles\n\n  def role_names\n    roles.pluck(:name)\n  end\nend\n```\n\n#### Definitions\n\nSome abilities:\n\n```ruby\nclass Administrator \u003c Warhol::Ability\n  define_permissions do\n    can :manage, :all\n\n    # object is always included in scope\n    can :other_condition, user_id: object.id\n\n    # user via additional accessor\n    can :thing_with_condition, user_id: user.id\n  end\nend\n\nclass Member \u003c Warhol::Ability\n  define_permissions do\n    can :read, :all\n  end\nend\n```\n\nNow, we just check the role. People using Rails can just invoke `can?` from their controller instead of explicitly making a new `Ability` class:\n\n```\nuser\nputs user.role_names\n# =\u003e ['Member']\nputs Ability.new(user).can? :manage, @something\n# =\u003e false\n```\n\nThat's it!\n\n### Config Options\n\nTo configure Warhol, we create a new singleton configuration class. Every time you invoke `::new`, the instance is replaced with the one you most recently defined. \n\n```ruby\nWarhol::Config.new do |warhol|\n  warhol.option = value\nend\n```\n| Key                      | Type          | Description                                                                                                                                                                                                                                           |\n|--------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| ability_parent           | Module        | The parent object under which to define the `Ability` constant.                                                                                                                                                                                       |\n| additional_accessors     | Array[String] | By default, inside the `define_permissions` block you can access the object you are performing the check on by invoking `object`. For the example use case of applying user permissions, passing `%w(user)` may not be a bad idea.                    |\n| role_accessor (REQUIRED) | Symbol        | Accessor used to fetch roles from domain object.                                                                                                                                                                                                      |\n| role_proc                | Proc          | If you do not wish to define an accessor, you can pass a block with an arity of 1; the object you are performing the check against will be passed as an argument allowing you to either implement the logic here or delegate it to a service object.  |\n\n\n### Advanced Usage\n\n#### Map domain object to roles\n\nSome elect to not store role data on user objects, but rather pass users to a service object that provides its authorization level. You can do this with Warhol by providing it with a `proc`:\n\n```ruby\nWarhol::Config.new do |warhol|\n  warhol.role_proc = proc do |user|\n    PermissionService.new(user).roles\n  end\nend\n```\n\n#### Override parent namespace of `Ability`\n\nIf for some reason you would like to bind `Ability` somewhere other than `Object::Ability`, you can provide an alternate namespace. \n\n```ruby\nWarhol::Config.new do |warhol|\n  warhol.ability_parent = Foo::Bar::Baz\nend\n```\n\n`Foo::Bar::Baz::Ability` will now be where it is placed. \n\n\n## License\n\nMIT License\n\nCopyright (c) 2017 David Stancu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmach-kernel%2Fwarhol","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmach-kernel%2Fwarhol","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmach-kernel%2Fwarhol/lists"}