Skip to content
Newer
Older
100644 354 lines (262 sloc) 14.6 KB
3a24300 @nathanl renamed gem to 'authority'
authored
1 # Authority
7e96a6b @nathanl initial commit
authored
2
69ad496 @nathanl Can now specify custom security violation handler
authored
3 Authority gives you a clean and easy way to say, in your Rails app, **who** is allowed to do **what** with your models, and to handle and/or log unauthorized actions.
4a4b9e7 @nathanl More README updates
authored
4
379841a @nathanl Tiny README tweaks
authored
5 It requires that you already have some kind of user object in your application, accessible from all controllers and views via a method like `current_user` (configurable).
4a4b9e7 @nathanl More README updates
authored
6
b7081f0 @nathanl Add build status indicator to README
authored
7 [![Build Status](https://secure.travis-ci.org/nathanl/authority.png)](http://travis-ci.org/nathanl/authority)
8
e1ab32e @nathanl Massive README reorg
authored
9 ## Contents
10
2eaa868 @nathanl Resort to HTML; Markdown fails on 3-deep lists?
authored
11 <ul>
12 <li><a href="#overview">Overview</a></li>
13 <li><a href="#flow_of_authority">The flow of Authority</a></li>
14 <li><a href="#installation">Installation</a></li>
15 <li><a href="#defining_your_abilities">Defining Your Abilities</a></li>
16 <li><a href="#wiring_it_together">Wiring It Together</a>
17 <ul>
18 <li><a href="#users">Users</a></li>
19 <li><a href="#models">Models</a></li>
20 <li><a href="#authorizers">Authorizers</a>
21 <ul>
22 <li><a href="#default_strategies">Default strategies</a></li>
23 <li><a href="#testing_authorizers">Testing Authorizers</a></li>
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
24 <li><a href="#custom_authorizers">Custom Authorizers</a></li>
2eaa868 @nathanl Resort to HTML; Markdown fails on 3-deep lists?
authored
25 </ul></li>
26 <li><a href="#controllers">Controllers</a></li>
27 <li><a href="#views">Views</a></li>
28 </ul></li>
29 <li><a href="#security_violations_and_logging">Security Violations &amp; Logging</a></li>
30 <li><a href="#credits">Credits</a></li>
31 <li><a href="#contributing">Contributing</a></li>
32 </ul>
e1ab32e @nathanl Massive README reorg
authored
33
34 <a name="overview">
1b4be57 @nathanl First pass at docs
authored
35 ## Overview
36
3a24300 @nathanl renamed gem to 'authority'
authored
37 The goals of Authority are:
1b4be57 @nathanl First pass at docs
authored
38
39 - To allow broad, class-level rules. Examples:
9d6607d @nathanl Documentation corrections
authored
40 - "Basic users cannot delete any Widget."
1b4be57 @nathanl First pass at docs
authored
41 - "Only admin users can create Offices."
42 - To allow fine-grained, instance-level rules. Examples:
9d6607d @nathanl Documentation corrections
authored
43 - "Management users can only edit schedules with date ranges in the future."
1b4be57 @nathanl First pass at docs
authored
44 - "Users can't create playlists more than 20 songs long unless they've paid."
45 - To provide a clear syntax for permissions-based views. Example:
9d6607d @nathanl Documentation corrections
authored
46 - `link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)`
1b4be57 @nathanl First pass at docs
authored
47 - To gracefully handle any access violations: display a "you can't do that" screen and log the violation.
e1ab32e @nathanl Massive README reorg
authored
48 - To do all this with minimal effort and mess.
7e96a6b @nathanl initial commit
authored
49
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
50 Notably, Authority is **ORM-neutral** and has **no magical rules syntax**. Authority helps your classes communicate, but your authorization logic goes in normal Ruby methods.
51
e1ab32e @nathanl Massive README reorg
authored
52 <a name="flow_of_authority">
95f9dd1 @nathanl Update README
authored
53 ## The flow of Authority
54
379841a @nathanl Tiny README tweaks
authored
55 Authority encapsulates all authorization logic in `Authorizer` classes. Want to do something with a model? **Ask its authorizer**.
95f9dd1 @nathanl Update README
authored
56
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
57 You can group models under authorizers in any way you wish. For example:
58
59
73ffdd6 @nathanl Slight diagram skinnyfication
authored
60 +-------------+ +--------------+ +-------------+
61 |Simplest case| |Logical groups| |Most granular|
62 +-------------+ +--------------+ +-------------+
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
63
73ffdd6 @nathanl Slight diagram skinnyfication
authored
64 default_strategy default_strategy default_strategy
65 + + +
66 | +--------+-------+ +-------------------+-------------------+
67 + + + + + +
68 EverythingAuthorizer BasicAuthorizer AdminAuthorizer CommentAuthorizer ArticleAuthorizer EditionAuthorizer
69 + + + + + +
70 +-------+-------+ +-+ +------+ | | |
71 + + + + + + + + +
72 Comment Article Edition Comment Article Edition Comment Article Edition
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
73
74
e1ab32e @nathanl Massive README reorg
authored
75 The process generally flows like this:
76
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
77 current_user.can_create?(Moose) # You ask this question, and the user
78 + # automatically asks the model...
79 |
80 v
81 Moose.creatable_by?(current_user) # The model automatically asks
82 + # its authorizer...
83 |
84 v
85 MooseAuthorizer.creatable_by?(current_user) # *You define this method.*
73ffdd6 @nathanl Slight diagram skinnyfication
authored
86 + # If it's missing, the default
87 | # strategy is used...
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
88 v
89 config.default_strategy.call(:creatable, MooseAuthorizer, user) # *You define this strategy.*
90
91 If the answer is `false` and the original caller was a controller, this is treated as a `SecurityViolation`. If it was a view, maybe you just don't show a link.
92
93 (Diagrams made with [AsciiFlow](http://asciiflow.com))
95f9dd1 @nathanl Update README
authored
94
e1ab32e @nathanl Massive README reorg
authored
95 <a name="installation">
7e96a6b @nathanl initial commit
authored
96 ## Installation
97
e1ab32e @nathanl Massive README reorg
authored
98 Starting from a clean commit status, add `authority` to your Gemfile, `bundle`, then `rails g authority:install`.
7e96a6b @nathanl initial commit
authored
99
e1ab32e @nathanl Massive README reorg
authored
100 <a name="defining_your_abilities">
101 ## Defining Your Abilities
7e96a6b @nathanl initial commit
authored
102
e1ab32e @nathanl Massive README reorg
authored
103 Edit `config/initializers/authority.rb`. That file documents all your options, but one of particular interest is `config.abilities`, which defines the verbs and corresponding adjectives in your app. The defaults are:
7e96a6b @nathanl initial commit
authored
104
1deedd4 @nathanl Will Github highlight this nicely?
authored
105 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
106 config.abilities = {
107 :create => 'creatable',
108 :read => 'readable',
109 :update => 'updatable',
110 :delete => 'deletable'
111 }
1deedd4 @nathanl Will Github highlight this nicely?
authored
112 ```
c92e4d9 @nathanl More info in generator template and more docs
authored
113
e1ab32e @nathanl Massive README reorg
authored
114 This option determines what methods are added to your users, models and authorizers. If you need to ask `user.can_deactivate?(Satellite)` and `@satellite.deactivatable_by?(user)`, add those to the hash.
c92e4d9 @nathanl More info in generator template and more docs
authored
115
e1ab32e @nathanl Massive README reorg
authored
116 <a name="wiring_it_together">
117 ## Wiring It Together
7e96a6b @nathanl initial commit
authored
118
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
119 <a name="users">
1b4be57 @nathanl First pass at docs
authored
120 ### Users
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
121
1deedd4 @nathanl Will Github highlight this nicely?
authored
122 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
123 # Whatever class represents a logged-in user in your app
124 class User
125 # Adds `can_create?(resource)`, etc
126 include Authority::Abilities
127 ...
128 end
1deedd4 @nathanl Will Github highlight this nicely?
authored
129 ```
4a4b9e7 @nathanl More README updates
authored
130
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
131 <a name="models">
f6acb7a @nathanl buncha pending tests
authored
132 ### Models
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
133
1deedd4 @nathanl Will Github highlight this nicely?
authored
134 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
135 class Article
136 # Adds `creatable_by?(user)`, etc
137 include Authority::Abilities
138
139 # Without this, ArticleAuthorizer is assumed
140 self.authorizer_name = 'AdminAuthorizer'
141 ...
142 end
1deedd4 @nathanl Will Github highlight this nicely?
authored
143 ```
1b4be57 @nathanl First pass at docs
authored
144
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
145 <a name="authorizers">
1b4be57 @nathanl First pass at docs
authored
146 ### Authorizers
147
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
148 Add your authorizers under `app/authorizers`, subclassing `Authority::Authorizer`.
1b4be57 @nathanl First pass at docs
authored
149
e1ab32e @nathanl Massive README reorg
authored
150 These are where your actual authorization logic goes. Here's how it works:
1b4be57 @nathanl First pass at docs
authored
151
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
152 - Instance methods answer questions about model instances, like "can this user update this **particular** widget?" (Within an instance method, you can get the model instance with `resource`).
153 - Any instance method you don't define (for example, if you didn't make a `def deletable_by?(user)`) will fall back to the corresponding class method. In other words, if you haven't said whether a user can update **this particular** widget, we'll decide by checking whether they can update **any** widget.
e1ab32e @nathanl Massive README reorg
authored
154 - Class methods answer questions about model classes, like "is it **ever** permissible for this user to update a Widget?"
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
155 - Any class method you don't define (for example, if you didn't make a `def self.updatable_by?(user)`) will fall back to your configurable [default strategy](#default_strategies).
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
156
e1ab32e @nathanl Massive README reorg
authored
157 For example:
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
158
1deedd4 @nathanl Will Github highlight this nicely?
authored
159 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
160 # app/authorizers/schedule_authorizer.rb
161 class ScheduleAuthorizer < Authority::Authorizer
162 # Class method: can this user at least sometimes create a Schedule?
163 def self.creatable_by?(user)
164 user.manager?
165 end
166
167 # Instance method: can this user delete this particular schedule?
168 def deletable_by?(user)
169 resource.in_future? && user.manager? && resource.department == user.department
170 end
171 end
1deedd4 @nathanl Will Github highlight this nicely?
authored
172 ```
1b4be57 @nathanl First pass at docs
authored
173
e1ab32e @nathanl Massive README reorg
authored
174 As you can see, you can specify different logic for every method on every model, if necessary. On the other extreme, you could simply supply a [default strategy](#default_strategies) that covers all your use cases.
1b4be57 @nathanl First pass at docs
authored
175
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
176 <a name="default_strategies">
c6c4e47 @nathanl Describe testing authorizers
authored
177 #### Default Strategies
e1ab32e @nathanl Massive README reorg
authored
178
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
179 Any class method you don't define on an authorizer will use your default strategy. The **default** default strategy simply returns `false`, meaning that everything is forbidden. This whitelisting approach will keep you from accidentally allowing things you didn't intend.
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
180
e1ab32e @nathanl Massive README reorg
authored
181 You can configure a different default strategy. For example, you might want one that looks up permissions in your database:
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
182
1deedd4 @nathanl Will Github highlight this nicely?
authored
183 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
184 # In config/initializers/authority.rb
6ece493 @nathanl Do..end looks nicer here
authored
185 config.default_strategy = Proc.new do |able, authorizer, user|
3ce2f2f @nathanl use Github-flavored markdown
authored
186 # Does the user have any of the roles which give this permission?
187 (roles_which_grant(able, authorizer) & user.roles).any?
6ece493 @nathanl Do..end looks nicer here
authored
188 end
1deedd4 @nathanl Will Github highlight this nicely?
authored
189 ```
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
190
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
191 If your system is uniform enough, **this strategy alone might handle all the logic you need**.
192
c6c4e47 @nathanl Describe testing authorizers
authored
193 <a name="testing_authorizers">
194 #### Testing Authorizers
195
196 One nice thing about putting your authorization logic in authorizers is the ease of testing. Here's a brief example.
197
1deedd4 @nathanl Will Github highlight this nicely?
authored
198 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
199 # An authorizer shared by several admin-only models
200 describe AdminAuthorizer do
c6c4e47 @nathanl Describe testing authorizers
authored
201
3ce2f2f @nathanl use Github-flavored markdown
authored
202 before :each do
203 @user = FactoryGirl.build(:user)
204 @admin = FactoryGirl.build(:admin)
205 end
c6c4e47 @nathanl Describe testing authorizers
authored
206
3ce2f2f @nathanl use Github-flavored markdown
authored
207 describe "class" do
208 it "should let admins update in bulk" do
209 AdminAuthorizer.should be_bulk_updatable_by(@admin)
210 end
c6c4e47 @nathanl Describe testing authorizers
authored
211
3ce2f2f @nathanl use Github-flavored markdown
authored
212 it "should not let users update in bulk" do
213 AdminAuthorizer.should_not be_bulk_updatable_by(@user)
214 end
215 end
c6c4e47 @nathanl Describe testing authorizers
authored
216
3ce2f2f @nathanl use Github-flavored markdown
authored
217 describe "instances" do
c6c4e47 @nathanl Describe testing authorizers
authored
218
3ce2f2f @nathanl use Github-flavored markdown
authored
219 before :each do
220 # A mock model that uses AdminAuthorizer
221 @admin_resource_instance = mock_admin_resource
222 end
c6c4e47 @nathanl Describe testing authorizers
authored
223
3ce2f2f @nathanl use Github-flavored markdown
authored
224 it "should not allow users to delete" do
225 @admin_resource_instance.authorizer.should_not be_deletable_by(@user)
226 end
c6c4e47 @nathanl Describe testing authorizers
authored
227
3ce2f2f @nathanl use Github-flavored markdown
authored
228 end
c6c4e47 @nathanl Describe testing authorizers
authored
229
3ce2f2f @nathanl use Github-flavored markdown
authored
230 end
1deedd4 @nathanl Will Github highlight this nicely?
authored
231 ```
c6c4e47 @nathanl Describe testing authorizers
authored
232
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
233 <a name="custom_authorizers">
234 #### Custom Authorizers
235
236 If you want to customize your authorizers even further - for example, maybe you want them all to have a method like `has_permission?(user, permission_name)` - you can insert a custom class into the inheritance chain.
237
1deedd4 @nathanl Will Github highlight this nicely?
authored
238 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
239 # lib/my_app/authorizer.rb
240 module MyApp
241 class Authorizer < Authority::Authorizer
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
242
3ce2f2f @nathanl use Github-flavored markdown
authored
243 def self.has_permission(user, permission_name)
244 # look that up somewhere
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
245 end
3ce2f2f @nathanl use Github-flavored markdown
authored
246
247 end
248 end
249
250 #app/authorizers/badger_authorizer.rb
251 class BadgerAuthorizer < MyApp::Authorizer
252 # contents
253 end
1deedd4 @nathanl Will Github highlight this nicely?
authored
254 ```
85fca47 @nathanl Diagrams! Everybody loves 'em.
authored
255
256 If you decide to place your custom class in `lib` as shown above (as opposed to putting it in `app`), you should require it at the bottom of `config/initializers/authority.rb`.
257
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
258 <a name="controllers">
adac9d2 @nathanl Improvements to README
authored
259 ### Controllers
260
c8f1b1a @nathanl Rename SecurityTransgression to SecurityViolation
authored
261 Anytime a controller finds a user attempting something they're not authorized to do, a [Security Violation](#security_violations_and_logging) will result. Controllers get two ways to check authorization:
adac9d2 @nathanl Improvements to README
authored
262
e1ab32e @nathanl Massive README reorg
authored
263 - `authorize_actions_for Transaction` protects multiple controller actions with a `before_filter`, which performs a class-level check. If the current user is never allowed to delete a `Transaction`, they'll never even get to the controller's `destroy` method.
264 - `authorize_action_for @transaction` can be called inside a single controller action, and performs an instance-level check. If called inside `update`, it will check whether the current user is allowed to update this particular `@transaction` instance.
adac9d2 @nathanl Improvements to README
authored
265
e1ab32e @nathanl Massive README reorg
authored
266 The relationship between controller actions and abilities - like checking `readable_by?` on the `index` action - is configurable both globally, using `config.controller_action_map`, and per controller, as below.
adac9d2 @nathanl Improvements to README
authored
267
1deedd4 @nathanl Will Github highlight this nicely?
authored
268 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
269 class LlamaController < ApplicationController
a86a6d6 @nathanl Improved documentation for controller methods
authored
270
3ce2f2f @nathanl use Github-flavored markdown
authored
271 # Check class-level authorizations before all actions except :create
272 # Before this controller's 'neuter' action, ask whether current_user.can_update?(Llama)
273 authorize_actions_for Llama, :actions => {:neuter => :update}, :except => :create
274
275 # Before this controller's 'breed' action, ask whether current_user.can_create?(Llama)
276 authority_action :breed => 'new'
a86a6d6 @nathanl Improved documentation for controller methods
authored
277
3ce2f2f @nathanl use Github-flavored markdown
authored
278 ...
a86a6d6 @nathanl Improved documentation for controller methods
authored
279
3ce2f2f @nathanl use Github-flavored markdown
authored
280 def edit
281 @llama = Llama.find(params[:id])
282 @llama.attributes = params[:llama] # Don't save the attributes before authorizing
283 authorize_action_for(@llama) # failure == SecurityViolation
284 if @llama.save?
285 # etc
286 end
a86a6d6 @nathanl Improved documentation for controller methods
authored
287
3ce2f2f @nathanl use Github-flavored markdown
authored
288 end
1deedd4 @nathanl Will Github highlight this nicely?
authored
289 ```
15bfa3c @nathanl More README tweaking
authored
290
e1ab32e @nathanl Massive README reorg
authored
291 <a name="views">
292 ### Views
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
293
294 Assuming your user object is available in your views, you can do all kinds of conditional rendering. For example:
295
1deedd4 @nathanl Will Github highlight this nicely?
authored
296 ```ruby
3ce2f2f @nathanl use Github-flavored markdown
authored
297 link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)
1deedd4 @nathanl Will Github highlight this nicely?
authored
298 ```
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
299
c8f1b1a @nathanl Rename SecurityTransgression to SecurityViolation
authored
300 If the user isn't allowed to edit widgets, they won't see the link. If they're nosy and try to hit the URL directly, they'll get a [Security Violation](#security_violations_and_logging) from the controller.
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
301
e1ab32e @nathanl Massive README reorg
authored
302 <a name="security_violations_and_logging">
303 ## Security Violations & Logging
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
304
69ad496 @nathanl Can now specify custom security violation handler
authored
305 Anytime a user attempts an unauthorized action, Authority calls whatever controller method is specified by your `security_violation_handler` option, handing it the exception. The default handler is `authority_forbidden`, which Authority adds to your `ApplicationController`. It does the following:
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
306
69ad496 @nathanl Can now specify custom security violation handler
authored
307 - Renders `public/403.html`
c263bd3 @nathanl More README improvements. IT SHALL BE PERFECT, OH YES.
authored
308 - Logs the violation to whatever logger you configured.
309
69ad496 @nathanl Can now specify custom security violation handler
authored
310 You can specify a different handler like so:
15bfa3c @nathanl More README tweaking
authored
311
69ad496 @nathanl Can now specify custom security violation handler
authored
312 ```ruby
313 # config/initializers/authority.rb
314 ...
315 config.security_violation_handler = :fire_ze_missiles
316 ...
317
318 # app/controllers/application_controller.rb
319 class ApplicationController < ActionController::Base
320
321 def fire_ze_missiles(exception)
322 # Log? Set a flash message? Dispatch minions to fill their mailbox with goose droppings? It's up to you.
323 end
324 ...
325 end
326 ```
6623df9 @nathanl Update README
authored
327
69ad496 @nathanl Can now specify custom security violation handler
authored
328 If you want different error handling per controller, define `fire_ze_missiles` on each of them.
6623df9 @nathanl Update README
authored
329
e1ab32e @nathanl Massive README reorg
authored
330 <a name="credits">
15bfa3c @nathanl More README tweaking
authored
331 ## Credits, AKA 'Shout-Outs'
1b4be57 @nathanl First pass at docs
authored
332
7dbb8b2 @nathanl Revised shout-outs
authored
333 - [adamhunter](https://github.com/adamhunter) for pairing with me on this gem. The only thing faster than his typing is his brain.
334 - [nkallen](https://github.com/nkallen) for writing [a lovely blog post on access control](http://pivotallabs.com/users/nick/blog/articles/272-access-control-permissions-in-rails) when he worked at Pivotal Labs. I cried sweet tears of joy when I read that a couple of years ago. I was like, "Zee access code, she is so BEEUTY-FUL!"
335 - [jnunemaker](https://github.com/jnunemaker) for later creating [Canable](http://github.com/jnunemaker/canable), another inspiration for Authority.
336 - [TMA](http://www.tma1.com) for employing me and letting me open source some of our code.
1b4be57 @nathanl First pass at docs
authored
337
e1ab32e @nathanl Massive README reorg
authored
338 <a name="contributing">
7e96a6b @nathanl initial commit
authored
339 ## Contributing
340
22682b5 @nathanl couple more small doc update
authored
341 What should you contribute? Try the TODO file for ideas, or grep the project for 'TODO' comments.
2dabdeb @nathanl Move TODOs to separate file
authored
342
343 How can you contribute?
344
2db5ab8 @nathanl A brief discourse on the necessity of communication
authored
345 1. Let's talk! Before you do a bunch of work, open an issue so we can be sure we agree.
346 2. Fork this project
347 3. Create your feature branch (`git checkout -b my-new-feature`)
348 4. `bundle install` to get all dependencies
349 5. `rspec spec` to run all tests.
350 6. Update/add tests for your changes and code until they pass.
351 7. Commit your changes (`git commit -am 'Added some feature'`)
352 8. Push to the branch (`git push origin my-new-feature`)
353 9. Create a new Pull Request
Something went wrong with that request. Please try again.