{"id":15628309,"url":"https://github.com/jnunemaker/canable","last_synced_at":"2025-04-04T16:15:31.272Z","repository":{"id":824364,"uuid":"538835","full_name":"jnunemaker/canable","owner":"jnunemaker","description":"Simple Ruby authorization system.","archived":false,"fork":false,"pushed_at":"2013-04-11T15:17:27.000Z","size":192,"stargazers_count":311,"open_issues_count":3,"forks_count":17,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-28T15:08:30.470Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://jnunemaker.github.com/canable/","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/jnunemaker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-02-27T17:26:36.000Z","updated_at":"2024-10-29T15:06:30.000Z","dependencies_parsed_at":"2022-07-05T17:32:21.867Z","dependency_job_id":null,"html_url":"https://github.com/jnunemaker/canable","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Fcanable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Fcanable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Fcanable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Fcanable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jnunemaker","download_url":"https://codeload.github.com/jnunemaker/canable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247208151,"owners_count":20901570,"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":[],"created_at":"2024-10-03T10:21:51.871Z","updated_at":"2025-04-04T16:15:31.252Z","avatar_url":"https://github.com/jnunemaker.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Canable\n\nSimple Ruby authorization system.\n\n## Install\n\n```\ngem install canable\n```\n\n## Cans\n\nWhatever class you want all permissions to run through should include Canable::Cans.\n\n```ruby\nclass User\n  include MongoMapper::Document\n  include Canable::Cans\nend\n```\n\nThis means that an instance of a user automatically gets can methods for the default REST actions: `can_view?(resource)`, `can_create?(resource)`, `can_update?(resource)`, `can_destroy?(resource)`.\n\n## Ables\n\nEach of the can methods simply calls the related \"able\" method (viewable, creatable, updatable, destroyable) for the action (view, create, update, delete). Canable comes with defaults for this methods that you can then override as makes sense for your permissions.\n\n```ruby\nclass Article\n  include MongoMapper::Document\n  include Canable::Ables\nend\n```\n\nIncluding Canable::Ables adds the able methods to the class including it. In this instance, article now has `viewable_by?(user)`, `creatable_by?(user)`, `updatable_by?(user)` and `destroyable_by?(user)`.\n\nLets say an article can be viewed and created by anyone, but only updated or destroyed by the user that created the article. To do that, you could leave `viewable_by?` and `creatable_by?` alone as they default to true and just override the other methods.\n\n```ruby\nclass Article\n  include MongoMapper::Document\n  include Canable::Ables\n  userstamps! # adds creator and updater\n\n  def updatable_by?(user)\n    creator == user\n  end\n\n  def destroyable_by?(user)\n    updatable_by?(user)\n  end\nend\n```\n\nLet's look at some sample code now:\n\n```ruby\njohn = User.create(:name =\u003e 'John')\nsteve = User.create(:name =\u003e 'Steve')\n\nruby = Article.new(:title =\u003e 'Ruby')\njohn.can_create?(ruby) # true\nsteve.can_create?(ruby) # true\n\nruby.creator = john\nruby.save\n\njohn.can_view?(ruby) # true\nsteve.can_view?(ruby) # true\n\njohn.can_update?(ruby) # true\nsteve.can_update?(ruby) # false\n\njohn.can_destroy?(ruby) # true\nsteve.can_destroy?(ruby) # false\n```\n\nNow we can implement our permissions for each resource and then always check whether a user can or cannot do something. This makes it all really easy to test. In one common pattern, a single permission flag controls whether or not users can perform multiple administrator-specific operations. Canable can honor that flag with:\n\n```ruby\ndef writable_by?(user)\n  user.can_do_anything?\nend\nalias_method :creatable_by?, :writable_by?\nalias_method :updatable_by?, :writable_by?\nalias_method :destroyable_by?, :writable_by?\n```\n\nNext, how would you use this in the controller. \n\n## Enforcers\n\n```ruby\nclass ApplicationController\n  include Canable::Enforcers\nend\n```\n\nIncluding `Canable::Enforcers` adds an enforce permission method for each of the actions defined (by default view/create/update/destroy). It is the same thing as doing this for each Canable action:\n\n```ruby\nclass ApplicationController\n  include Canable::Enforcers\n\n  delegate :can_view?, :to =\u003e :current_user\n  helper_method :can_view? # so you can use it in your views\n  hide_action :can_view?\n\n  private\n    def enforce_view_permission(resource)\n      raise Canable::Transgression unless can_view?(resource)\n    end\nend\n```\n\nWhich means you can use it like this:\n\n```ruby\nclass ArticlesController \u003c ApplicationController\n  def show\n    @article = Article.find!(params[:id])\n    enforce_view_permission(@article)\n  end\nend\n```\n\nIf the user `can_view?` the article, all is well. If not, a `Canable::Transgression` is raised which you can decide how to handle (show 404, slap them on the wrist, etc.). For example:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  rescue_from Canable::Transgression, :with =\u003e :render_403\n\n  protected\n  def render_403(e)\n    # notify normal exception handler(s) here\n    render :status =\u003e 403\n  end\n```\n\n## Adding Your Own Actions\n\nYou can add your own actions like this:\n\n```ruby\nCanable.add(:publish, :publishable)\n```\n\nThe first parameter is the can method (ie: `can_publish?`) and the second is the able method (ie: `publishable_by?`).\n\nAbles can also be added as class methods. For example, to restrict access to an index action:\n\n```ruby\nCanable.add(:index, :indexable)\n```\n\nThen enforce by passing the class instead of the instance:\n\n```ruby\nclass ArticlesController \u003c ApplicationController\n  def index\n    @articles = Article.all\n    enforce_index_permission(Article)\n  end\nend\n```\n\nThen in the article model, add the able check as a class method:\n\n```ruby\nclass Article\n  # ...\n  def self.indexable_by?(user)\n    !user.nil?\n  end\nend\n```\n\n## Review\n\nSo, lets review: cans go on user model, ables go on everything, you override ables in each model where you want to enforce permissions, and enforcers go after each time you find or initialize an object in a controller. Bing, bang, boom.\n\n## Contributing\n\n* Fork the project.\n* Make your feature addition or bug fix.\n* Add tests for it. This is important so I don't break it in a\n  future version unintentionally.\n* Commit, do not mess with rakefile, version, or history.\n  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)\n* Send me a pull request. Bonus points for topic branches.\n\n## Copyright\n\nCopyright (c) 2010 John Nunemaker. See LICENSE for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjnunemaker%2Fcanable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjnunemaker%2Fcanable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjnunemaker%2Fcanable/lists"}