{"id":14969810,"url":"https://github.com/ukazap/permisi","last_synced_at":"2025-10-25T05:18:56.285Z","repository":{"id":47787347,"uuid":"335790289","full_name":"ukazap/permisi","owner":"ukazap","description":"Simple and dynamic role-based access control for Rails","archived":false,"fork":false,"pushed_at":"2021-08-13T01:56:59.000Z","size":114,"stargazers_count":12,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-01-31T18:03:32.572Z","etag":null,"topics":["authorization","rails","rbac"],"latest_commit_sha":null,"homepage":"","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/ukazap.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-02-04T00:11:31.000Z","updated_at":"2024-06-10T05:21:47.000Z","dependencies_parsed_at":"2022-08-20T16:00:07.819Z","dependency_job_id":null,"html_url":"https://github.com/ukazap/permisi","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukazap%2Fpermisi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukazap%2Fpermisi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukazap%2Fpermisi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukazap%2Fpermisi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ukazap","download_url":"https://codeload.github.com/ukazap/permisi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238301283,"owners_count":19449414,"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":["authorization","rails","rbac"],"created_at":"2024-09-24T13:42:25.679Z","updated_at":"2025-10-25T05:18:51.247Z","avatar_url":"https://github.com/ukazap.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"If you're viewing this at https://github.com/ukazap/permisi, you're reading the documentation for the main branch. [Go to specific version.](https://github.com/ukazap/permisi/blob/main/CHANGELOG.md)\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003e\n      \u003ca href=\"https://commons.wikimedia.org/wiki/File:Female_Chinese_Lion_Statue.jpg\"\u003e\n        \u003cimg src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Female_Chinese_Lion_Statue.jpg/102px-Female_Chinese_Lion_Statue.jpg\"\u003e\n      \u003c/a\u003e\n    \u003c/th\u003e\n    \u003cth\u003e\n      \u003ch1\u003ePermisi\u003c/h1\u003e\n      \u003cp\u003e\u003cem\u003eSimple and dynamic role-based access control for Rails\u003c/em\u003e\u003c/p\u003e\n      \u003cp\u003e\n        \u003ca href=\"https://badge.fury.io/rb/permisi\"\u003e\n          \u003cimg src=\"https://badge.fury.io/rb/permisi.svg\" alt=\"Gem Version\"\u003e\n        \u003c/a\u003e\n        \u003ca href=\"https://codeclimate.com/github/ukazap/permisi/maintainability\"\u003e\n          \u003cimg src=\"https://api.codeclimate.com/v1/badges/0b1238302f2012b20740/maintainability\" /\u003e\n        \u003c/a\u003e\n        \u003ca href=\"https://codecov.io/gh/ukazap/permisi\"\u003e\n          \u003cimg src=\"https://codecov.io/gh/ukazap/permisi/branch/main/graph/badge.svg?token=9YRMVFCDA8\"/\u003e\n        \u003c/a\u003e\n      \u003c/p\u003e\n    \u003c/th\u003e\n    \u003cth\u003e\n      \u003ca href=\"https://commons.wikimedia.org/wiki/File:Male_Chinese_Lion_Statue.jpg\"\u003e\n        \u003cimg src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Male_Chinese_Lion_Statue.jpg/98px-Male_Chinese_Lion_Statue.jpg\"\u003e\n      \u003c/a\u003e\n    \u003c/th\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## Concept\n\nPermisi provides a way of dynamically declaring user rights (a.k.a. permissions) using a simple role-based access control scheme.\n\nThis is not an alternative to CanCanCan/Pundit, instead it complement them with dynamic role definition and role membership.\n\nPermisi has three basic concepts:\n\n- Actor: a person, group of people, or an automated agent who interacts with the app\n- Role: a job function, job title, or rank which determines an actor's authority\n- Permission: the ability to perform an action\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'permisi'\n```\n\nAnd then execute:\n\n    $ bundle install\n    $ rails g permisi:install\n\n## Configuring backend\n\nSet `config.backend` in the initializer to the backend of choice for storing and retrieving roles:\n\n```ruby\n# config/initializers/permisi.rb\n\nPermisi.init do |config|\n  #...\n  config.backend = :active_record\n  #...\nend\n```\n\nTo use `:active_record`, run the generated migration from the installation step:\n\n    $ rails db:migrate\n\nPermisi only support `:active_record` backend at the moment. In the future, it will be possible to use `:mongoid`.\n\n## Configuring permissions\n\nFirst you have to predefine the permissions, which is basically a set of possible actions according to the app's use cases. The actions can be grouped in any way possible. For example, you might want to define actions around resource types.\n\nTo define the available actions in the system, assign a hash to the `config.permissions` with the following format:\n\n```ruby\n# config/initializers/permisi.rb\nPermisi.init do |config|\n  # ...\n  config.permissions = {\n    # A symbol-array pair denotes a namespace.\n    # A common use of namespacing is for grouping\n    # available actions by resources.\n    authors: [\n      # Enclosed in the array are symbols\n      # denoting available actions in the namespace:\n      :list,\n      :view,\n      :create,\n      :edit,\n      :delete\n    ],\n    # You can also use the simplified %i[] notation:\n    publishers: %i[list view create edit delete],\n    # Besides actions, you can also have nested\n    # namespaces:\n    books: [\n      :list,\n      :view,\n      :create,\n      :edit,\n      :delete,\n      {\n        editions: [\n          :list, :view, :create, :edit, :delete, :archive\n        ]\n      }\n    ]\n  }\n  # ...\nend\n```\n\n## Defining and managing roles\n\nOnce you have the predefined permissions, you can then define different roles with different level of access within the boundary of the predefined permissions. You can delete or create new roles according to organizational changes. You can also modify existing roles without a change in your code.\n\nYou can create, edit, and destroy roles at runtime. You might also want to define preset roles via `db/seeds.rb`.\n\n```ruby\n# Interact with Permisi.roles as you would with ActiveRecord query interfaces:\n\n# List all roles\nPermisi.roles.all\n\n# Create a new role\nadmin_role = Permisi.roles.create(slug: :admin, name: \"Administrator\", permissions: {\n  books: {\n    list: true,\n    view: true,\n    create: true,\n    edit: true\n  }\n})\n\n# Ask specific role permission\nadmin_role.allows?(\"books.delete\") # == false\n\n# Update existing role\nadmin_role.permissions[:books].merge!({ delete: true })\nadmin_role.save\nadmin_role.allows?(\"books.delete\") # == true\n```\n\n## Configuring actors\n\nYou can then give or take multiple roles to an actor which will allow or prevent them to perform certain actions in a flexible manner. But before you can do that, you have to wire up your user model with Permisi using via `Permisi::Actable` mixin.\n\nPermisi does not hold an assumption that a specific model is present (e.g. User model). Instead, it keeps track of \"actors\" internally. The goal is to support multiple use cases such as actor polymorphism, user _groups_, etc.\n\nFor example, you can map your user model to Permisi's actor model like so:\n\n```ruby\n# app/models/user.rb\n\nclass User \u003c ApplicationRecord\n  include Permisi::Actable\nend\n```\n\nYou can then interact using `#permisi` method:\n\n```ruby\nuser = User.find_by_email(\"esther@example.com\")\nuser.permisi # =\u003e instance of Actor\n\nadmin_role = Permisi.roles.find_by_slug(:admin)\nadmin_role.allows?(\"books.delete\") # == true\n\nuser.permisi.roles \u003c\u003c admin_role\n\nuser.permisi.role?(:admin) # == true\nuser.permisi.has_role?(:admin) # == user.permisi.role? :admin\n\nuser.permisi.may_i?(\"books.delete\") # == true\nuser.permisi.may?(\"books.delete\") # == user.permisi.may_i? \"books.delete\"\n\nuser.permisi.roles.destroy(admin_role)\n\nuser.permisi.role?(:admin) # == false\nuser.permisi.may_i?(\"books.delete\") # == false\n```\n\n## Caching\n\nPermisi has several optimizations out of the box: actor roles eager loading, actor permissions memoization, and the optional actor permissions caching.\n\n### Actor roles eager loading\n\nAlthough checking whether an actor has a role goes against a good RBAC practice, it is still possible on Permisi. Calling `role?` multiple times will only make one call to the database:\n\n```ruby\nuser = User.find_by_email(\"esther@example.com\")\nuser.permisi.role?(:admin) # eager loads roles\nuser.permisi.role?(:admin) # uses the eager-loaded roles\nuser.permisi.has_role?(:admin) # uses the eager-loaded roles\n```\n\n### Actor permissions memoization\n\nTo check whether or not an actor is allowed to perform a specific action (`#may_i?`), Permisi will check on the actor's permissions which is constructed in the following steps:\n\n- load all the roles an actor have from the database\n- initialize an empty aggregate hash\n- for each role, merge its permissions hash to the aggregate hash\n\nDeserializing the hashes from the database and deeply-merging them into an aggregate hash can be expensive, so it will only happen to an instance of actor only once through memoization.\n\n### Actor permissions caching\n\nAlthough memoization helps, the permission hash construction will still occur every time an actor is initialized. To alleviate this, we can introduce a caching layer so that we can skip the hash construction for fresh actors. You must configure a cache store to use caching:\n\n```ruby\n# config/initializers/permisi.rb\n\nPermisi.init do |config|\n  # You can use the default Rails cache store\n  config.cache_store = Rails.cache\n  # or use other cache stores\n  config.cache_store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV['REDIS_URL'])\n  # or\n  config.cache_store = ActiveSupport::Cache::FileStore.new(\"/home/ukazap/permisi_cache/\")\nend\n```\n\nYou can also roll your own [custom cache store](https://guides.rubyonrails.org/caching_with_rails.html#custom-cache-stores).\n\n### Cache/memo invalidation\n\nThe following will trigger actor's permissions cache/memo invalidation:\n\n- adding roles to the actor\n- removing roles from the actor\n- editing roles that belongs to the actor\n\n## Contributing\n\nFor development and how to submit improvements, please refer to the [contribution guide](https://github.com/ukazap/permisi/blob/main/CONTRIBUTING.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Permisi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ukazap/permisi/blob/main/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fukazap%2Fpermisi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fukazap%2Fpermisi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fukazap%2Fpermisi/lists"}