{"id":13747547,"url":"https://github.com/makandra/consul","last_synced_at":"2025-05-14T14:10:22.856Z","repository":{"id":37405715,"uuid":"1604177","full_name":"makandra/consul","owner":"makandra","description":"Scope-based authorization for Ruby on Rails.","archived":false,"fork":false,"pushed_at":"2025-01-31T08:32:45.000Z","size":414,"stargazers_count":334,"open_issues_count":0,"forks_count":37,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-04-14T02:57:43.213Z","etag":null,"topics":["authorization","rails"],"latest_commit_sha":null,"homepage":"https://makandra.com/","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/makandra.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":"2011-04-12T13:40:38.000Z","updated_at":"2025-03-25T15:21:52.000Z","dependencies_parsed_at":"2024-12-15T05:00:27.460Z","dependency_job_id":"45d42d1b-38f2-4789-8cf1-65f3f7a54ed4","html_url":"https://github.com/makandra/consul","commit_stats":{"total_commits":202,"total_committers":27,"mean_commits":7.481481481481482,"dds":0.5099009900990099,"last_synced_commit":"039380119e1d4b8ce22d187790588a045708b1ed"},"previous_names":[],"tags_count":46,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/makandra%2Fconsul","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/makandra%2Fconsul/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/makandra%2Fconsul/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/makandra%2Fconsul/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/makandra","download_url":"https://codeload.github.com/makandra/consul/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254160558,"owners_count":22024571,"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"],"created_at":"2024-08-03T06:01:33.163Z","updated_at":"2025-05-14T14:10:17.848Z","avatar_url":"https://github.com/makandra.png","language":"Ruby","readme":"\u003cp\u003e\n  \u003ca href=\"https://makandra.de/\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"media/makandra-with-bottom-margin.light.svg\"\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"media/makandra-with-bottom-margin.dark.svg\"\u003e\n      \u003cimg align=\"right\" width=\"25%\" alt=\"makandra\" src=\"media/makandra-with-bottom-margin.light.svg\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"media/logo.light.shapes.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"media/logo.dark.shapes.svg\"\u003e\n    \u003cimg width=\"155\" alt=\"consul\" role=\"heading\" aria-level=\"1\" src=\"media/logo.light.shapes.svg\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n[![Tests](https://github.com/makandra/consul/workflows/Tests/badge.svg)](https://github.com/makandra/consul/actions)\n\nConsul is an authorization solution for Ruby on Rails where you describe _sets of accessible things_ to control what a user can see or edit.\n\nWe have used Consul in combination with [assignable_values](https://github.com/makandra/assignable_values) to solve a variety of authorization requirements ranging from boring to bizarre.\nAlso see our crash course video: [Solving bizare authorization requirements with Rails](http://bizarre-authorization.talks.makandra.com/).\n\nConsul is tested with Rails 6.1, 7.1, 7.2 and 8.0 on Ruby 2.5, 2.7, 3.2, 3.3 (only if supported, for each Ruby/Rails combination). If you need support for Rails 3.2, please use [v0.13.2](https://github.com/makandra/consul/tree/v0.13.2).\n\n## Describing access to your application\n\nYou describe access to your application by putting a `Power` model into `app/models/power.rb`.\nInside your `Power` you can talk about what is accessible for the current user, e.g.\n\n- [A scope of records a user may see](#scope-powers-relations)\n- [Whether the user is allowed to use a particular screen](#boolean-powers)\n- [A list of values a user may assign to a particular attribute](#validating-assignable-values)\n\nA `Power` might look like this:\n\n```rb\nclass Power\n  include Consul::Power\n\n  def initialize(user)\n    @user = user\n  end\n\n  power :users do\n    User if @user.admin?\n  end\n\n  power :notes do\n    Note.by_author(@user)\n  end\n\n  power :dashboard do\n    true # not a scope, but a boolean power. This is useful to control access to stuff that doesn't live in the database.\n  end\n\nend\n```\n\nThere are no restrictions on the name or constructor arguments of this class.\n\nYou can deposit all kinds of objects in your power. See the sections below for details.\n\n### Scope powers (relations)\n\nA typical use case in a Rails application is to restrict access to your ActiveRecord models. For example:\n\n- Anonymous visitors may only see public posts\n- Users may only see their own notes\n- Only admins may edit users\n\nYou do this by making your powers return an ActiveRecord scope (or \"relation\"):\n\n```rb\nclass Power\n  ...\n\n  power :notes do\n    Note.by_author(@user)\n  end\n\n  power :users do\n    User if @user.admin?\n  end\n\nend\n```\n\nYou can now query these powers in order to retrieve the scope:\n\n```rb\npower = Power.new(user)\npower.notes  # =\u003e returns an ActiveRecord::Scope\n```\n\nOr you can ask if the power is given (meaning it's not `nil`):\n\n```rb\npower.notes? # =\u003e returns true if Power#notes returns a scope and not nil\n```\n\nOr you can raise an error unless a power is given, e.g. to guard access into a controller action:\n\n```rb\npower.notes! # =\u003e raises Consul::Powerless unless Power#notes returns a scope (even if it's empty)\n```\n\nOr you ask whether a given record is included in its scope (can be [optimized](#optimizing-record-checks-for-scope-powers)):\n\n```rb\npower.note?(Note.last) # =\u003e returns whether the given Note is in the Power#notes scope. Caches the result for subsequent queries.\n```\n\nOr you can raise an error unless a given record is included in its scope:\n\n```rb\npower.note!(Note.last) # =\u003e raises Consul::Powerless unless the given Note is in the Power#notes scope\n```\n\nSee our crash course video [Solving bizare authorization requirements with Rails](http://bizarre-authorization.talks.makandra.com/) for many different use cases you can cover with this pattern.\n\n### Defining different powers for different actions\n\nIf you have different access rights for e.g. viewing or updating posts, simply use different powers:\n\n```rb\nclass Power\n  ...\n\n  power :notes do\n    Note.published\n  end\n\n  power :updatable_notes do\n    Note.by_author(@user)\n  end\n\n  power :destroyable_notes do\n    Note if @user.admin?\n  end\n\nend\n```\n\nThere is also a [shortcut to map different powers to RESTful controller actions](#protect-entry-into-controller-actions).\n\n### Boolean powers\n\nBoolean powers are useful to control access to stuff that doesn't live in the database:\n\n```rb\nclass Power\n  ...\n\n  power :dashboard do\n    true\n  end\n\nend\n```\n\nYou can query it like the other powers:\n\n```rb\npower = Power.new(@user)\npower.dashboard? # =\u003e true\npower.dashboard! # =\u003e raises Consul::Powerless unless Power#dashboard? returns true\n```\n\n### Powers that give no access at all\n\nNote that there is a difference between having access to an empty list of records, and having no access at all.\nIf you want to express that a user has no access at all, make the respective power return `nil`.\n\nNote how the power in the example below returns `nil` unless the user is an admin:\n\n```rb\nclass Power\n  ...\n\n  power :users do\n    User if @user.admin?\n  end\n\nend\n```\n\nWhen a non-admin queries the `:users` power, she will get the following behavior:\n\n```rb\npower = Power.new(@user)\npower.users # =\u003e returns nil\npower.users? # =\u003e returns false\npower.users! # =\u003e raises Consul::Powerless\npower.user?(User.last) # =\u003e returns false\npower.user!(User.last) # =\u003e raises Consul::Powerless\n```\n\n### Powers that only check a given object\n\nSometimes it is not convenient to define powers as a collection or scope (relation).\nSometimes you only want to store a method that checks whether a given object is accessible.\n\nTo do so, simply define a power that ends in a question mark:\n\n```rb\nclass Power\n  ...\n\n  power :updatable_post? do |post|\n    post.author == @user\n  end\n\nend\n```\n\nYou can query such an power as always:\n\n```rb\npower = Power.new(@user)\npower.updatable_post?(Post.last) # return true if the author of the post is @user\npower.updatable_post!(Post.last) # raises Consul::Powerless unless the author of the post is @user\n```\n\n### Other types of powers\n\nA power can return any type of object. For instance, you often want to return an array:\n\n```rb\nclass Power\n  ...\n\n  power :assignable_note_states do\n    if admin?\n      %w[draft pending published retracted]\n    else\n      %w[draft pending]\n    end\n  end\n\nend\n```\n\nYou can query it like any other power. E.g. if a non-admin queries this power she will get the following behavior:\n\n```rb\npower.assignable_note_states # =\u003e ['draft', 'pending']\npower.assignable_note_states? # =\u003e returns true\npower.assignable_note_states! # =\u003e does nothing (because the power isn't nil)\npower.assignable_note_state?('draft') # =\u003e returns true\npower.assignable_note_state?('published') # =\u003e returns false\npower.assignable_note_state!('published') # =\u003e raises Consul::Powerless\n```\n\n### Defining multiple powers at once\n\nYou can define multiple powers at once by giving multiple power names:\n\n```rb\nclass Power\n  ...\n\n  power :destroyable_users, :updatable_users do\n    User if admin?\n  end\n\nend\n```\n\n### Powers that require context (arguments)\n\nSometimes it can be useful to define powers that require context. To do so, just take an argument in your `power` block:\n\n```rb\nclass Power\n  ...\n\n  power :client_notes do |client|\n    client.notes.where(:state =\u003e 'published')\n  end\n\nend\n```\n\nWhen querying such a power, you always need to provide the context, e.g.:\n\n```rb\nclient = ...\nnote = ...\nPower.current.client_note?(client, note)\n```\n\n### Optimizing record checks for scope powers\n\nYou can query a scope power for a given record, e.g.\n\n```rb\nclass Power\n  ...\n\n  power :posts do |post|\n    Post.where(:author_id =\u003e @user.id)\n  end\nend\n\npower = Power.new(@user)\npower.post?(Post.last)\n```\n\nWhat Consul does internally is fetch **all** the IDs of the `power.posts` scope and test if the given\nrecord's ID is among them. This list of IDs is cached for subsequent calls, so you will only touch the database once.\n\nAs scary as it might sound, fetching all IDs of a scope scales quiet nicely for many thousand records. There will\nhowever be the point where you want to optimize this.\n\nWhat you can do in Consul is to define a second power that checks a given record in plain Ruby:\n\n```rb\nclass Power\n  ...\n\n  power :posts do |post|\n    Post.where(:author_id =\u003e @user.id)\n  end\n\n  power :post? do |post|\n    post.author_id == @user.id\n  end\n\nend\n```\n\nThis way you do not need to touch the database at all.\n\n## Role-based permissions\n\nConsul has no built-in support for role-based permissions, but you can easily implement it yourself. Let's say your `User` model has a string column `role` which can be `\"author\"` or `\"admin\"`:\n\n```rb\nclass Power\n  include Consul::Power\n\n  def initialize(user)\n    @user = user\n  end\n\n  power :notes do\n    case role\n      when :admin then Note\n      when :author then Note.by_author\n    end\n  end\n\n  private\n\n  def role\n    @user.role.to_sym\n  end\n\nend\n```\n\n## Controller integration\n\nIt is convenient to expose the power for the current request to the rest of the application. Consul will help you with that if you tell it how to instantiate a power for the current request:\n\n```rb\nclass ApplicationController \u003c ActionController::Base\n  include Consul::Controller\n\n  current_power do\n    Power.new(current_user)\n  end\n\nend\n```\n\nYou now have a helper method `current_power` for your controller and views. Everywhere else, you can access it from `Power.current`. The power will be instantiated when the request is handed over from routing to `ApplicationController`, and will be nilified once the request was processed.\n\nYou can now use power scopes to control access:\n\n```rb\nclass NotesController \u003c ApplicationController\n\n  def show\n    @note = current_power.notes.find(params[:id])\n  end\n\nend\n```\n\n### Protect entry into controller actions\n\nTo make sure a power is given before every action in a controller:\n\n```rb\nclass NotesController \u003c ApplicationController\n  power :notes\nend\n```\n\nYou can use `:except` and `:only` options like in before_actions.\n\nYou can also map different powers to different actions:\n\n```rb\nclass NotesController \u003c ApplicationController\n  power :notes, :map =\u003e { [:edit, :update, :destroy] =\u003e :changeable_notes }\nend\n```\n\nActions that are not listed in `:map` will get the default action `:notes`.\n\nNote that in moderately complex authorization scenarios you will often find yourself writing a map like this:\n\n```rb\nclass NotesController \u003c ApplicationController\n  power :notes, :map =\u003e {\n    [:edit, :update] =\u003e :updatable_notes,\n    [:new, :create] =\u003e :creatable_notes,\n    [:destroy] =\u003e :destroyable_notes\n  }\nend\n```\n\nBecause this pattern is so common, there is a shortcut `:crud` to do the same:\n\n```rb\nclass NotesController \u003c ApplicationController\n  power :crud =\u003e :notes\nend\n```\n\nAnd if your power [requires context](#powers-that-require-context-arguments) (is parametrized), you can give it using the `:context` method:\n\n```rb\nclass ClientNotesController \u003c ApplicationController\n\n  power :client_notes, :context =\u003e :load_client\n\n  private\n\n  def load_client\n    @client ||= Client.find(params[:client_id])\n  end\n\nend\n```\n\n### Auto-mapping a power scope to a controller method\n\nIt is often convenient to map a power scope to a private controller method:\n\n```rb\nclass NotesController \u003c ApplicationController\n\n  power :notes, :as =\u003e :note_scope\n\n  def show\n    @note = note_scope.find(params[:id])\n  end\n\nend\n```\n\nThe mapped method is aware of the `:map` option.\n\nThe mapped method can be overridden and access the original implementation using `super`:\n\n```ruby\nclass NotesController \u003c ApplicationController\n\n  power :notes, :as =\u003e :note_scope\n\n  # ...\n\n  def note_scope\n    super.where(trashed: false)\n  end\n\nend\n```\n\n### Multiple power-mappings for nested resources\n\nWhen using [nested resources](http://guides.rubyonrails.org/routing.html#nested-resources) you probably want two power\nchecks and method mappings: One for the parent resource, another for the child resource.\n\nSay you have the following routes:\n\n```rb\nresources :clients do\n  resources :notes\nend\n```\n\nAnd the following power definitions:\n\n```rb\nclass Power\n  ...\n\n  power :clients do\n    Client.active if signed_in?\n  end\n\n  power :client_notes do |client|\n    client.notes.where(:state =\u003e 'published')\n  end\n\nend\n```\n\nYou can now check and map both powers in the nested `NotesController`:\n\n```rb\nclass NotesController \u003c ApplicationController\n\n  power :clients, :as =\u003e :client_scope\n  power :client_notes, :context =\u003e :load_client, :as =\u003e :note_scope\n\n  def show\n    load_note\n  end\n\n  private\n\n  def load_client\n    @client ||= client_scope.find(params[:client_id])\n  end\n\n  def load_note\n    @note ||= note_scope.find(params[:id])\n  end\n\nend\n```\n\nNote how we provide the `Client` parameter for the `:client_notes` power by using the `:context =\u003e :load_client`\noption in the `power` directive.\n\n### How to never forget a power check\n\nYou can force yourself to use a `power` check in every controller. This will raise `Consul::UncheckedPower` if you ever forget it:\n\n```rb\nclass ApplicationController \u003c ActionController::Base\n  include Consul::Controller\n  require_power_check\nend\n```\n\nNote that this check is satisfied by _any_ `.power` directive in the controller class or its ancestors, even if that `.power` directive has `:only` or `:except` options that do not apply to the current action.\n\nShould you want to forego the power check (e.g. to remove authorization checks from an entirely public controller):\n\n```rb\nclass ApiController \u003c ApplicationController\n  skip_power_check\nend\n```\n\n## Validating assignable values\n\nSometimes a scope is not enough to express what a user can edit. You will often want to give a user write access to a record, but restrict the values she can assign to a given field.\n\nConsul leverages the [assignable_values](https://github.com/makandra/assignable_values) gem to add an optional authorization layer to your models. This layer adds additional validations in the context of a request, but skips those validations in other contexts (console, background jobs, etc.).\n\nYou can enable the authorization layer by using the macro `authorize_values_for`:\n\n```rb\nclass Story \u003c ActiveRecord::Base\n  authorize_values_for :state\nend\n```\n\nThe macro defines an accessor `power` on instances of `Story`. If that field is set to a power, the values of `state` will be validated against a whitelist of values provided by that power. If that field is `nil`, the validation is skipped.\n\nHere is a power implementation that can provide a list of assignable values for the example above:\n\n```rb\nclass Power\n  ...\n\n  def assignable_story_states(story)\n    if admin?\n      ['delivered', 'accepted', 'rejected']\n    else\n      ['delivered']\n    end\n  end\n\nend\n```\n\nHere you can see how to activate the authorization layer and use the new validations:\n\n```rb\nstory = Story.new\nPower.current = Power.new(:role =\u003e :guest) # activate the authorization layer\n\nstory.assignable_states # ['delivered'] # apparently we're not admins\n\nstory.state = 'accepted' # a disallowed value\nstory.valid? # =\u003e false\n\nstory.state = 'delivered' # an allowed value\nstory.valid? # =\u003e true\n```\n\nYou can not only authorize scalar attributes like strings or integers that way, you can also authorize `belongs_to` associations:\n\n```rb\nclass Story \u003c ActiveRecord::Base\n  belongs_to :project\n  authorize_values_for :project\nend\n\nclass Power\n  ...\n\n  power :assignable_story_projects do |story|\n    user.account.projects\n  end\nend\n```\n\nThe `authorize_values_for` macro comes with many useful options and details best explained in the [assignable_values README](https://github.com/makandra/assignable_values), so head over there for more. The macro is basically a shortcut for this:\n\n```rb\nassignable_values_for :field, :through =\u003e lambda { Power.current }\n```\n\n## Memoization\n\nAll power methods are [memoized](https://www.justinweiss.com/articles/4-simple-memoization-patterns-in-ruby-and-one-gem/) for performance reasons. Multiple calls to the same method will only call your block the first time, and return a cached result afterwards:\n\n```\npower = Power.new\npower.projects! # calls the `power :projects { ... }` block\npower.projects! # returns the cached result from earlier\npower.projects! # returns the cached result from earlier\n```\n\nIf you want to discard all cached results, call `#unmemoize_all`:\n\n```\npower.unmemoize_all\n```\n\n## Dynamic power access\n\nConsul gives you a way to dynamically access and query powers for a given name, model class or record.\nA common use case for this are generic helper methods, e.g. a method to display an \"edit\" link for any given record\nif the user is authorized to change that record:\n\n```rb\nmodule CrudHelper\n\n  def edit_record_action(record)\n    if current_power.include_record?(:updatable, record)\n      link_to 'Edit', [:edit, record]\n    end\n  end\n\nend\n```\n\nYou can find a full list of available dynamic calls below:\n\n| Dynamic call                                           | Equivalent                                 |\n| ------------------------------------------------------ | ------------------------------------------ |\n| `Power.current.send(:notes)`                           | `Power.current.notes`                      |\n| `Power.current.include_power?(:notes)`                 | `Power.current.notes?`                     |\n| `Power.current.include_power!(:notes)`                 | `Power.current.notes!`                     |\n| `Power.current.include_object?(:notes, Note.last)`     | `Power.current.note?(Note.last)`           |\n| `Power.current.include_object!(:notes, Note.last)`     | `Power.current.note!(Note.last)`           |\n| `Power.current.for_model(Note)`                        | `Power.current.notes`                      |\n| `Power.current.for_model(:updatable, Note)`            | `Power.current.updatable_notes`            |\n| `Power.current.include_model?(Note)`                   | `Power.current.notes?`                     |\n| `Power.current.include_model?(:updatable, Note)`       | `Power.current.updatable_notes?`           |\n| `Power.current.include_model!(Note)`                   | `Power.current.notes!`                     |\n| `Power.current.include_model!(:updatable, Note)`       | `Power.current.updatable_notes!`           |\n| `Power.current.include_record?(Note.last)`             | `Power.current.note?(Note.last)`           |\n| `Power.current.include_record?(:updatable, Note.last)` | `Power.current.updatable_note?(Note.last)` |\n| `Power.current.include_record!(Note.last)`             | `Power.current.note!(Note.last)`           |\n| `Power.current.include_record!(:updatable, Note.last)` | `Power.current.updatable_note!(Note.last)` |\n| `Power.current.name_for_model(Note)`                   | `:notes`                                   |\n| `Power.current.name_for_model(:updatable, Note)`       | `:updatable_notes`                         |\n\n## Querying a power that might be nil\n\nYou will often want to access `Power.current` from another model, to e.g. iterate through the list of accessible users:\n\n```rb\nclass UserReport\n\n  def data\n    Power.current.users.collect do |user|\n      [user.name, user.email, user.income]\n    end\n  end\n\nend\n```\n\nGood practice is for your model to not crash when `Power.current` is `nil`. This is the case when your model isn't\ncalled as part of processing a browser request, e.g. on the console, during tests and during batch processes.\nIn such cases your model should simply skip authorization and assume that all users are accessible:\n\n```rb\nclass UserReport\n\n  def data\n    accessible_users = Power.current.present? ? Power.current.users : User\n    accessible_users.collect do |user|\n      [user.name, user.email, user.income]\n    end\n  end\n\nend\n```\n\nBecause this pattern is so common, the `Power` class comes with a number of class methods you can use to either query\n`Power.current` or, if it is not set, just assume that everything is accessible:\n\n```rb\nclass UserReport\n\n  def data\n    Power.for_model(User).collect do |user|\n      [user.name, user.email, user.income]\n    end\n  end\n\nend\n```\n\nThere is a long selection of class methods that behave neutrally in case `Power.current` is `nil`:\n\n| Call                                           | Equivalent                                                          |\n| ---------------------------------------------- | ------------------------------------------------------------------- |\n| `Power.for_model(Note)`                        | `Power.current.present? ? Power.current.notes : Note`               |\n| `Power.for_model(:updatable, Note)`            | `Power.current.present? ? Power.current.updatable_notes : Note`     |\n| `Power.include_model?(Note)`                   | `Power.current.present? ? Power.notes? : true`                      |\n| `Power.include_model?(:updatable, Note)`       | `Power.current.present? ? Power.updatable_notes? : true`            |\n| `Power.include_model!(Note)`                   | `Power.notes! if Power.current.present?`                            |\n| `Power.include_model!(:updatable, Note)`       | `Power.updatable_notes! if Power.current.present?`                  |\n| `Power.include_record?(Note.last)`             | `Power.current.present? ? Power.note?(Note.last) : true`            |\n| `Power.include_record?(:updatable, Note.last)` | `Power.current.present? ? Power.updatable_note?(Note.last?) : true` |\n| `Power.include_record!(Note.last)`             | `Power.note!(Note.last) if Power.current.present?`                  |\n| `Power.include_record!(:updatable, Note.last)` | `Power.updatable_note!(Note.last) if Power.current.present?`        |\n\n## Testing\n\nThis section Some hints for testing authorization with Consul.\n\n### Test that a controller checks against a power\n\nInclude the Consul Matcher `spec/support/consul_matchers.rb`:\n\n```\nrequire 'consul/spec/matchers'\n\nRSpec.configure do |c|\n  c.include Consul::Spec::Matchers\nend\n```\n\nYou can say this in any controller spec:\n\n```rb\ndescribe CakesController do\n\n  it { should check_power(:cakes) }\n\nend\n```\n\nYou can test against all options of the `power` macro:\n\n```rb\ndescribe CakesController do\n\n  it { should check_power(:cakes, :map =\u003e { [:edit, :update] =\u003e :updatable_cakes }) }\n\nend\n```\n\n### Temporarily change the current power\n\nWhen you set `Power.current` to a power in an RSpec example, you must remember to nilify it afterwards. Otherwise other examples will see your global changes.\n\nA better way is to use the `.with_power` method to change the current power for the duration of a block:\n\n```rb\nadmin = User.new(:role =\u003e 'admin')\nadmin_power = Power.new(admin)\n\nPower.with_power(admin_power) do\n  # run code that uses Power.current\nend\n```\n\n`Power.current` will be `nil` (or its former value) after the block has ended.\n\nA nice shortcut is that when you call `with_power` with an argument that is not already a `Power`, Consul will instantiate a `Power` for you:\n\n```rb\nadmin = User.new(:role =\u003e 'admin')\n\nPower.with_power(admin) do\n  # run code that uses Power.current\nend\n```\n\nThere is also a method `.without_power` that runs a block without a current Power:\n\n```rb\nPower.without_power do\n  # run code that should not see a Power\nend\n```\n\n## Installation\n\nAdd the following to your `Gemfile`:\n\n```\ngem 'consul'\n```\n\nNow run `bundle install` to lock the gem into your project.\n\n## Development\n\nWe currently develop using Ruby 3.4.1 (see `.ruby-version`) since that version works for current versions of ActiveRecord that we support. GitHub Actions will test additional Ruby versions (2.7.3, 3.2.0).\n\nThere are tests in `spec`. We only accept PRs with tests. To run tests:\n\n- Install Ruby 3.4.1\n- run `bundle install`\n- Put your database credentials into `spec/support/database.yml`. There's a `database.sample.yml` you can use as a template.\n- There are gem bundles in the project root for each rails version that we support.\n- You can bundle all test applications by saying `bundle exec rake matrix:install`\n- You can run specs from the project root by saying `bundle exec rake matrix:spec`. This will run all gemfiles compatible with your current Ruby.\n\nIf you would like to contribute:\n\n- Fork the repository.\n- Push your changes **with specs**.\n- Send me a pull request.\n\nNote that we have configured GitHub Actions to automatically run tests in all supported Ruby versions and dependency sets after each push. We will only merge pull requests after a green GitHub Actions run.\n\nI'm very eager to keep this gem leightweight and on topic. If you're unsure whether a change would make it into the gem, [talk to me beforehand](mailto:henning.koch@makandra.de).\n\n## Credits\n\nHenning Koch from [makandra](http://makandra.com/)\n","funding_links":[],"categories":["Authorization","Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmakandra%2Fconsul","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmakandra%2Fconsul","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmakandra%2Fconsul/lists"}