Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Create gh-pages branch via GitHub

  • Loading branch information...
commit 036aaf8d3aa586d932402c958b5a254de7b25108 1 parent 9c4e034
John Nunemaker authored

Showing 2 changed files with 64 additions and 70 deletions. Show diff stats Hide diff stats

  1. +63 69 index.html
  2. +1 1  params.json
132 index.html
@@ -4,7 +4,7 @@
4 4 <meta charset="utf-8">
5 5 <meta http-equiv="X-UA-Compatible" content="chrome=1">
6 6 <title>Canable by jnunemaker</title>
7   -
  7 +
8 8 <link rel="stylesheet" href="stylesheets/styles.css">
9 9 <link rel="stylesheet" href="stylesheets/pygment_trac.css">
10 10 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
@@ -17,15 +17,22 @@
17 17 <header>
18 18 <h1>Canable</h1>
19 19 <p>Simple Ruby authorization system.</p>
  20 +
20 21 <p class="view"><a href="https://github.com/jnunemaker/canable">View the Project on GitHub <small>jnunemaker/canable</small></a></p>
  22 +
  23 +
21 24 <ul>
22 25 <li><a href="https://github.com/jnunemaker/canable/zipball/master">Download <strong>ZIP File</strong></a></li>
23 26 <li><a href="https://github.com/jnunemaker/canable/tarball/master">Download <strong>TAR Ball</strong></a></li>
24   - <li><a href="https://github.com/jnunemaker/canable">Fork On <strong>GitHub</strong></a></li>
  27 + <li><a href="https://github.com/jnunemaker/canable">View On <strong>GitHub</strong></a></li>
25 28 </ul>
26 29 </header>
27 30 <section>
28   - <h2>Install</h2>
  31 + <h1>Canable</h1>
  32 +
  33 +<p>Simple Ruby authorization system.</p>
  34 +
  35 +<h2>Install</h2>
29 36
30 37 <pre><code>gem install canable
31 38 </code></pre>
@@ -34,14 +41,11 @@
34 41
35 42 <p>Whatever class you want all permissions to run through should include Canable::Cans.</p>
36 43
37   -<div class="highlight">
38   -<pre><span class="k">class</span> <span class="nc">User</span>
39   - <span class="kp">include</span> <span class="no">MongoMapper</span><span class="o">::</span><span class="no">Document</span>
40   - <span class="kp">include</span> <span class="no">Canable</span><span class="o">::</span><span class="no">Cans</span>
  44 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">User</span>
  45 + <span class="kp">include</span> <span class="ss">MongoMapper</span><span class="p">:</span><span class="ss">:Document</span>
  46 + <span class="kp">include</span> <span class="ss">Canable</span><span class="p">:</span><span class="ss">:Cans</span>
41 47 <span class="k">end</span>
42   -</pre>
43   -</div>
44   -
  48 +</pre></div>
45 49
46 50 <p>This means that an instance of a user automatically gets can methods for the default REST actions: <code>can_view?(resource)</code>, <code>can_create?(resource)</code>, <code>can_update?(resource)</code>, <code>can_destroy?(resource)</code>.</p>
47 51
@@ -49,23 +53,19 @@
49 53
50 54 <p>Each 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.</p>
51 55
52   -<div class="highlight">
53   -<pre><span class="k">class</span> <span class="nc">Article</span>
54   - <span class="kp">include</span> <span class="no">MongoMapper</span><span class="o">::</span><span class="no">Document</span>
55   - <span class="kp">include</span> <span class="no">Canable</span><span class="o">::</span><span class="no">Ables</span>
  56 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">Article</span>
  57 + <span class="kp">include</span> <span class="ss">MongoMapper</span><span class="p">:</span><span class="ss">:Document</span>
  58 + <span class="kp">include</span> <span class="ss">Canable</span><span class="p">:</span><span class="ss">:Ables</span>
56 59 <span class="k">end</span>
57   -</pre>
58   -</div>
59   -
  60 +</pre></div>
60 61
61 62 <p>Including Canable::Ables adds the able methods to the class including it. In this instance, article now has <code>viewable_by?(user)</code>, <code>creatable_by?(user)</code>, <code>updatable_by?(user)</code> and <code>destroyable_by?(user)</code>.</p>
62 63
63 64 <p>Lets 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 <code>viewable_by?</code> and <code>creatable_by?</code> alone as they default to true and just override the other methods.</p>
64 65
65   -<div class="highlight">
66   -<pre><span class="k">class</span> <span class="nc">Article</span>
67   - <span class="kp">include</span> <span class="no">MongoMapper</span><span class="o">::</span><span class="no">Document</span>
68   - <span class="kp">include</span> <span class="no">Canable</span><span class="o">::</span><span class="no">Ables</span>
  66 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">Article</span>
  67 + <span class="kp">include</span> <span class="ss">MongoMapper</span><span class="p">:</span><span class="ss">:Document</span>
  68 + <span class="kp">include</span> <span class="ss">Canable</span><span class="p">:</span><span class="ss">:Ables</span>
69 69 <span class="n">userstamps!</span> <span class="c1"># adds creator and updater</span>
70 70
71 71 <span class="k">def</span> <span class="nf">updatable_by?</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
@@ -76,14 +76,11 @@
76 76 <span class="n">updatable_by?</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
77 77 <span class="k">end</span>
78 78 <span class="k">end</span>
79   -</pre>
80   -</div>
81   -
  79 +</pre></div>
82 80
83   -<p>Lets look at some sample code now:</p>
  81 +<p>Let's look at some sample code now:</p>
84 82
85   -<div class="highlight">
86   -<pre><span class="n">john</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="ss">:name</span> <span class="o">=&gt;</span> <span class="s1">'John'</span><span class="p">)</span>
  83 +<div class="highlight"><pre><span class="n">john</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="ss">:name</span> <span class="o">=&gt;</span> <span class="s1">'John'</span><span class="p">)</span>
87 84 <span class="n">steve</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="ss">:name</span> <span class="o">=&gt;</span> <span class="s1">'Steve'</span><span class="p">)</span>
88 85
89 86 <span class="n">ruby</span> <span class="o">=</span> <span class="no">Article</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:title</span> <span class="o">=&gt;</span> <span class="s1">'Ruby'</span><span class="p">)</span>
@@ -101,27 +98,31 @@
101 98
102 99 <span class="n">john</span><span class="o">.</span><span class="n">can_destroy?</span><span class="p">(</span><span class="n">ruby</span><span class="p">)</span> <span class="c1"># true</span>
103 100 <span class="n">steve</span><span class="o">.</span><span class="n">can_destroy?</span><span class="p">(</span><span class="n">ruby</span><span class="p">)</span> <span class="c1"># false</span>
104   -</pre>
105   -</div>
  101 +</pre></div>
106 102
  103 +<p>Now 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:</p>
107 104
108   -<p>Now 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. Next, how would you use this in the controller.</p>
  105 +<div class="highlight"><pre><span class="k">def</span> <span class="nf">writable_by?</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
  106 + <span class="n">user</span><span class="o">.</span><span class="n">can_do_anything?</span>
  107 +<span class="k">end</span>
  108 +<span class="n">alias_method</span> <span class="ss">:creatable_by?</span><span class="p">,</span> <span class="ss">:writable_by?</span>
  109 +<span class="n">alias_method</span> <span class="ss">:updatable_by?</span><span class="p">,</span> <span class="ss">:writable_by?</span>
  110 +<span class="n">alias_method</span> <span class="ss">:destroyable_by?</span><span class="p">,</span> <span class="ss">:writable_by?</span>
  111 +</pre></div>
  112 +
  113 +<p>Next, how would you use this in the controller. </p>
109 114
110 115 <h2>Enforcers</h2>
111 116
112   -<div class="highlight">
113   -<pre><span class="k">class</span> <span class="nc">ApplicationController</span>
114   - <span class="kp">include</span> <span class="no">Canable</span><span class="o">::</span><span class="no">Enforcers</span>
  117 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">ApplicationController</span>
  118 + <span class="kp">include</span> <span class="ss">Canable</span><span class="p">:</span><span class="ss">:Enforcers</span>
115 119 <span class="k">end</span>
116   -</pre>
117   -</div>
118   -
  120 +</pre></div>
119 121
120 122 <p>Including <code>Canable::Enforcers</code> 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:</p>
121 123
122   -<div class="highlight">
123   -<pre><span class="k">class</span> <span class="nc">ApplicationController</span>
124   - <span class="kp">include</span> <span class="no">Canable</span><span class="o">::</span><span class="no">Enforcers</span>
  124 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">ApplicationController</span>
  125 + <span class="kp">include</span> <span class="ss">Canable</span><span class="p">:</span><span class="ss">:Enforcers</span>
125 126
126 127 <span class="n">delegate</span> <span class="ss">:can_view?</span><span class="p">,</span> <span class="ss">:to</span> <span class="o">=&gt;</span> <span class="ss">:current_user</span>
127 128 <span class="n">helper_method</span> <span class="ss">:can_view?</span> <span class="c1"># so you can use it in your views</span>
@@ -129,73 +130,66 @@
129 130
130 131 <span class="kp">private</span>
131 132 <span class="k">def</span> <span class="nf">enforce_view_permission</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
132   - <span class="k">raise</span> <span class="no">Canable</span><span class="o">::</span><span class="no">Transgression</span> <span class="k">unless</span> <span class="n">can_view?</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
  133 + <span class="k">raise</span> <span class="ss">Canable</span><span class="p">:</span><span class="ss">:Transgression</span> <span class="k">unless</span> <span class="n">can_view?</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
133 134 <span class="k">end</span>
134 135 <span class="k">end</span>
135   -</pre>
136   -</div>
137   -
  136 +</pre></div>
138 137
139 138 <p>Which means you can use it like this:</p>
140 139
141   -<div class="highlight">
142   -<pre><span class="k">class</span> <span class="nc">ArticlesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
  140 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">ArticlesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
143 141 <span class="k">def</span> <span class="nf">show</span>
144 142 <span class="vi">@article</span> <span class="o">=</span> <span class="no">Article</span><span class="o">.</span><span class="n">find!</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:id</span><span class="o">]</span><span class="p">)</span>
145 143 <span class="n">enforce_view_permission</span><span class="p">(</span><span class="vi">@article</span><span class="p">)</span>
146 144 <span class="k">end</span>
147 145 <span class="k">end</span>
148   -</pre>
149   -</div>
  146 +</pre></div>
  147 +
  148 +<p>If the user <code>can_view?</code> the article, all is well. If not, a <code>Canable::Transgression</code> is raised which you can decide how to handle (show 404, slap them on the wrist, etc.). For example:</p>
150 149
  150 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">ApplicationController</span> <span class="o">&lt;</span> <span class="ss">ActionController</span><span class="p">:</span><span class="ss">:Base</span>
  151 + <span class="n">rescue_from</span> <span class="ss">Canable</span><span class="p">:</span><span class="ss">:Transgression</span><span class="p">,</span> <span class="ss">:with</span> <span class="o">=&gt;</span> <span class="ss">:render_403</span>
151 152
152   -<p>If the user <code>can_view?</code> the article, all is well. If not, a <code>Canable::Transgression</code> is raised which you can decide how to handle (show 404, slap them on the wrist, etc.).</p>
  153 + <span class="kp">protected</span>
  154 + <span class="k">def</span> <span class="nf">render_403</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
  155 + <span class="c1"># notify normal exception handler(s) here</span>
  156 + <span class="n">render</span> <span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">403</span>
  157 + <span class="k">end</span>
  158 +</pre></div>
153 159
154 160 <h2>Adding Your Own Actions</h2>
155 161
156 162 <p>You can add your own actions like this:</p>
157 163
158   -<div class="highlight">
159   -<pre><span class="no">Canable</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="ss">:publish</span><span class="p">,</span> <span class="ss">:publishable</span><span class="p">)</span>
160   -</pre>
161   -</div>
162   -
  164 +<div class="highlight"><pre><span class="no">Canable</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="ss">:publish</span><span class="p">,</span> <span class="ss">:publishable</span><span class="p">)</span>
  165 +</pre></div>
163 166
164 167 <p>The first parameter is the can method (ie: <code>can_publish?</code>) and the second is the able method (ie: <code>publishable_by?</code>).</p>
165 168
166 169 <p>Ables can also be added as class methods. For example, to restrict access to an index action:</p>
167 170
168   -<div class="highlight">
169   -<pre><span class="no">Canable</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="ss">:index</span><span class="p">,</span> <span class="ss">:indexable</span><span class="p">)</span>
170   -</pre>
171   -</div>
172   -
  171 +<div class="highlight"><pre><span class="no">Canable</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="ss">:index</span><span class="p">,</span> <span class="ss">:indexable</span><span class="p">)</span>
  172 +</pre></div>
173 173
174 174 <p>Then enforce by passing the class instead of the instance:</p>
175 175
176   -<div class="highlight">
177   -<pre><span class="k">class</span> <span class="nc">ArticlesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
  176 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">ArticlesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
178 177 <span class="k">def</span> <span class="nf">index</span>
179 178 <span class="vi">@articles</span> <span class="o">=</span> <span class="no">Article</span><span class="o">.</span><span class="n">all</span>
180 179 <span class="n">enforce_index_permission</span><span class="p">(</span><span class="no">Article</span><span class="p">)</span>
181 180 <span class="k">end</span>
182 181 <span class="k">end</span>
183   -</pre>
184   -</div>
185   -
  182 +</pre></div>
186 183
187 184 <p>Then in the article model, add the able check as a class method:</p>
188 185
189   -<div class="highlight">
190   -<pre><span class="k">class</span> <span class="nc">Article</span>
  186 +<div class="highlight"><pre><span class="k">class</span> <span class="nc">Article</span>
191 187 <span class="c1"># ...</span>
192 188 <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">indexable_by?</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
193 189 <span class="o">!</span><span class="n">user</span><span class="o">.</span><span class="n">nil?</span>
194 190 <span class="k">end</span>
195 191 <span class="k">end</span>
196   -</pre>
197   -</div>
198   -
  192 +</pre></div>
199 193
200 194 <h2>Review</h2>
201 195
@@ -213,7 +207,7 @@
213 207 <li>Send me a pull request. Bonus points for topic branches.</li>
214 208 </ul><h2>Copyright</h2>
215 209
216   -<p>Copyright (c) 2010 John Nunemaker. See <a href="https://github.com/jnunemaker/canable/blob/master/LICENSE">LICENSE</a> for details.</p>
  210 +<p>Copyright (c) 2010 John Nunemaker. See LICENSE for details.</p>
217 211 </section>
218 212 <footer>
219 213 <p>This project is maintained by <a href="https://github.com/jnunemaker">jnunemaker</a></p>
2  params.json
... ... @@ -1 +1 @@
1   -{"name":"Canable","body":"## Install\r\n\r\n```\r\ngem install canable\r\n```\r\n\r\n## Cans\r\n\r\nWhatever class you want all permissions to run through should include Canable::Cans.\r\n\r\n```ruby\r\nclass User\r\n include MongoMapper::Document\r\n include Canable::Cans\r\nend\r\n```\r\n\r\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)`.\r\n\r\n## Ables\r\n\r\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.\r\n\r\n```ruby\r\nclass Article\r\n include MongoMapper::Document\r\n include Canable::Ables\r\nend\r\n```\r\n\r\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)`.\r\n\r\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.\r\n\r\n```ruby\r\nclass Article\r\n include MongoMapper::Document\r\n include Canable::Ables\r\n userstamps! # adds creator and updater\r\n\r\n def updatable_by?(user)\r\n creator == user\r\n end\r\n\r\n def destroyable_by?(user)\r\n updatable_by?(user)\r\n end\r\nend\r\n```\r\n\r\nLets look at some sample code now:\r\n\r\n```ruby\r\njohn = User.create(:name => 'John')\r\nsteve = User.create(:name => 'Steve')\r\n\r\nruby = Article.new(:title => 'Ruby')\r\njohn.can_create?(ruby) # true\r\nsteve.can_create?(ruby) # true\r\n\r\nruby.creator = john\r\nruby.save\r\n\r\njohn.can_view?(ruby) # true\r\nsteve.can_view?(ruby) # true\r\n\r\njohn.can_update?(ruby) # true\r\nsteve.can_update?(ruby) # false\r\n\r\njohn.can_destroy?(ruby) # true\r\nsteve.can_destroy?(ruby) # false\r\n```\r\n\r\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. Next, how would you use this in the controller.\r\n\r\n## Enforcers\r\n\r\n```ruby\r\nclass ApplicationController\r\n include Canable::Enforcers\r\nend\r\n```\r\n\r\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:\r\n\r\n```ruby\r\nclass ApplicationController\r\n include Canable::Enforcers\r\n\r\n delegate :can_view?, :to => :current_user\r\n helper_method :can_view? # so you can use it in your views\r\n hide_action :can_view?\r\n\r\n private\r\n def enforce_view_permission(resource)\r\n raise Canable::Transgression unless can_view?(resource)\r\n end\r\nend\r\n```\r\n\r\nWhich means you can use it like this:\r\n\r\n```ruby\r\nclass ArticlesController < ApplicationController\r\n def show\r\n @article = Article.find!(params[:id])\r\n enforce_view_permission(@article)\r\n end\r\nend\r\n```\r\n\r\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.).\r\n\r\n## Adding Your Own Actions\r\n\r\nYou can add your own actions like this:\r\n\r\n```ruby\r\nCanable.add(:publish, :publishable)\r\n```\r\n\r\nThe first parameter is the can method (ie: `can_publish?`) and the second is the able method (ie: `publishable_by?`).\r\n\r\nAbles can also be added as class methods. For example, to restrict access to an index action:\r\n\r\n```ruby\r\nCanable.add(:index, :indexable)\r\n```\r\n\r\nThen enforce by passing the class instead of the instance:\r\n\r\n```ruby\r\nclass ArticlesController < ApplicationController\r\n def index\r\n @articles = Article.all\r\n enforce_index_permission(Article)\r\n end\r\nend\r\n```\r\n\r\nThen in the article model, add the able check as a class method:\r\n\r\n```ruby\r\nclass Article\r\n # ...\r\n def self.indexable_by?(user)\r\n !user.nil?\r\n end\r\nend\r\n```\r\n\r\n## Review\r\n\r\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.\r\n\r\n## Contributing\r\n\r\n* Fork the project.\r\n* Make your feature addition or bug fix.\r\n* Add tests for it. This is important so I don't break it in a\r\n future version unintentionally.\r\n* Commit, do not mess with rakefile, version, or history.\r\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)\r\n* Send me a pull request. Bonus points for topic branches.\r\n\r\n## Copyright\r\n\r\nCopyright (c) 2010 John Nunemaker. See [LICENSE](https://github.com/jnunemaker/canable/blob/master/LICENSE) for details.\r\n","tagline":"Simple Ruby authorization system.","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."}
  1 +{"name":"Canable","tagline":"Simple Ruby authorization system.","body":"# Canable\r\n\r\nSimple Ruby authorization system.\r\n\r\n## Install\r\n\r\n```\r\ngem install canable\r\n```\r\n\r\n## Cans\r\n\r\nWhatever class you want all permissions to run through should include Canable::Cans.\r\n\r\n```ruby\r\nclass User\r\n include MongoMapper::Document\r\n include Canable::Cans\r\nend\r\n```\r\n\r\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)`.\r\n\r\n## Ables\r\n\r\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.\r\n\r\n```ruby\r\nclass Article\r\n include MongoMapper::Document\r\n include Canable::Ables\r\nend\r\n```\r\n\r\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)`.\r\n\r\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.\r\n\r\n```ruby\r\nclass Article\r\n include MongoMapper::Document\r\n include Canable::Ables\r\n userstamps! # adds creator and updater\r\n\r\n def updatable_by?(user)\r\n creator == user\r\n end\r\n\r\n def destroyable_by?(user)\r\n updatable_by?(user)\r\n end\r\nend\r\n```\r\n\r\nLet's look at some sample code now:\r\n\r\n```ruby\r\njohn = User.create(:name => 'John')\r\nsteve = User.create(:name => 'Steve')\r\n\r\nruby = Article.new(:title => 'Ruby')\r\njohn.can_create?(ruby) # true\r\nsteve.can_create?(ruby) # true\r\n\r\nruby.creator = john\r\nruby.save\r\n\r\njohn.can_view?(ruby) # true\r\nsteve.can_view?(ruby) # true\r\n\r\njohn.can_update?(ruby) # true\r\nsteve.can_update?(ruby) # false\r\n\r\njohn.can_destroy?(ruby) # true\r\nsteve.can_destroy?(ruby) # false\r\n```\r\n\r\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:\r\n\r\n```ruby\r\ndef writable_by?(user)\r\n user.can_do_anything?\r\nend\r\nalias_method :creatable_by?, :writable_by?\r\nalias_method :updatable_by?, :writable_by?\r\nalias_method :destroyable_by?, :writable_by?\r\n```\r\n\r\nNext, how would you use this in the controller. \r\n\r\n## Enforcers\r\n\r\n```ruby\r\nclass ApplicationController\r\n include Canable::Enforcers\r\nend\r\n```\r\n\r\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:\r\n\r\n```ruby\r\nclass ApplicationController\r\n include Canable::Enforcers\r\n\r\n delegate :can_view?, :to => :current_user\r\n helper_method :can_view? # so you can use it in your views\r\n hide_action :can_view?\r\n\r\n private\r\n def enforce_view_permission(resource)\r\n raise Canable::Transgression unless can_view?(resource)\r\n end\r\nend\r\n```\r\n\r\nWhich means you can use it like this:\r\n\r\n```ruby\r\nclass ArticlesController < ApplicationController\r\n def show\r\n @article = Article.find!(params[:id])\r\n enforce_view_permission(@article)\r\n end\r\nend\r\n```\r\n\r\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:\r\n\r\n```ruby\r\nclass ApplicationController < ActionController::Base\r\n rescue_from Canable::Transgression, :with => :render_403\r\n\r\n protected\r\n def render_403(e)\r\n # notify normal exception handler(s) here\r\n render :status => 403\r\n end\r\n```\r\n\r\n## Adding Your Own Actions\r\n\r\nYou can add your own actions like this:\r\n\r\n```ruby\r\nCanable.add(:publish, :publishable)\r\n```\r\n\r\nThe first parameter is the can method (ie: `can_publish?`) and the second is the able method (ie: `publishable_by?`).\r\n\r\nAbles can also be added as class methods. For example, to restrict access to an index action:\r\n\r\n```ruby\r\nCanable.add(:index, :indexable)\r\n```\r\n\r\nThen enforce by passing the class instead of the instance:\r\n\r\n```ruby\r\nclass ArticlesController < ApplicationController\r\n def index\r\n @articles = Article.all\r\n enforce_index_permission(Article)\r\n end\r\nend\r\n```\r\n\r\nThen in the article model, add the able check as a class method:\r\n\r\n```ruby\r\nclass Article\r\n # ...\r\n def self.indexable_by?(user)\r\n !user.nil?\r\n end\r\nend\r\n```\r\n\r\n## Review\r\n\r\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.\r\n\r\n## Contributing\r\n\r\n* Fork the project.\r\n* Make your feature addition or bug fix.\r\n* Add tests for it. This is important so I don't break it in a\r\n future version unintentionally.\r\n* Commit, do not mess with rakefile, version, or history.\r\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)\r\n* Send me a pull request. Bonus points for topic branches.\r\n\r\n## Copyright\r\n\r\nCopyright (c) 2010 John Nunemaker. See LICENSE for details.\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."}

0 comments on commit 036aaf8

Please sign in to comment.
Something went wrong with that request. Please try again.