grape 0.3.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (101) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +70 -0
  4. data/.travis.yml +7 -6
  5. data/CHANGELOG.md +134 -4
  6. data/CONTRIBUTING.md +118 -0
  7. data/Gemfile +5 -2
  8. data/README.md +551 -116
  9. data/RELEASING.md +105 -0
  10. data/Rakefile +29 -8
  11. data/UPGRADING.md +124 -0
  12. data/grape.gemspec +3 -3
  13. data/lib/grape/api.rb +207 -88
  14. data/lib/grape/cookies.rb +4 -8
  15. data/lib/grape/endpoint.rb +198 -144
  16. data/lib/grape/error_formatter/base.rb +5 -7
  17. data/lib/grape/error_formatter/json.rb +3 -5
  18. data/lib/grape/error_formatter/txt.rb +1 -3
  19. data/lib/grape/error_formatter/xml.rb +4 -6
  20. data/lib/grape/exceptions/base.rb +9 -9
  21. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  22. data/lib/grape/exceptions/invalid_formatter.rb +1 -4
  23. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
  24. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
  25. data/lib/grape/exceptions/missing_mime_type.rb +1 -5
  26. data/lib/grape/exceptions/missing_option.rb +1 -4
  27. data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
  28. data/lib/grape/exceptions/unknown_options.rb +1 -5
  29. data/lib/grape/exceptions/unknown_validator.rb +1 -3
  30. data/lib/grape/exceptions/validation.rb +13 -3
  31. data/lib/grape/exceptions/validation_errors.rb +43 -0
  32. data/lib/grape/formatter/base.rb +5 -7
  33. data/lib/grape/formatter/json.rb +0 -3
  34. data/lib/grape/formatter/serializable_hash.rb +15 -15
  35. data/lib/grape/formatter/txt.rb +0 -2
  36. data/lib/grape/formatter/xml.rb +0 -2
  37. data/lib/grape/http/request.rb +26 -0
  38. data/lib/grape/locale/en.yml +8 -5
  39. data/lib/grape/middleware/auth/base.rb +30 -0
  40. data/lib/grape/middleware/auth/basic.rb +3 -20
  41. data/lib/grape/middleware/auth/digest.rb +2 -19
  42. data/lib/grape/middleware/auth/oauth2.rb +31 -24
  43. data/lib/grape/middleware/base.rb +7 -7
  44. data/lib/grape/middleware/error.rb +36 -22
  45. data/lib/grape/middleware/filter.rb +3 -3
  46. data/lib/grape/middleware/formatter.rb +99 -61
  47. data/lib/grape/middleware/globals.rb +13 -0
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  49. data/lib/grape/middleware/versioner/header.rb +22 -16
  50. data/lib/grape/middleware/versioner/param.rb +9 -11
  51. data/lib/grape/middleware/versioner/path.rb +10 -13
  52. data/lib/grape/middleware/versioner.rb +3 -1
  53. data/lib/grape/namespace.rb +23 -0
  54. data/lib/grape/parser/base.rb +3 -5
  55. data/lib/grape/parser/json.rb +0 -2
  56. data/lib/grape/parser/xml.rb +0 -2
  57. data/lib/grape/path.rb +70 -0
  58. data/lib/grape/route.rb +10 -6
  59. data/lib/grape/util/content_types.rb +2 -1
  60. data/lib/grape/util/deep_merge.rb +5 -5
  61. data/lib/grape/util/hash_stack.rb +13 -2
  62. data/lib/grape/validations/coerce.rb +11 -10
  63. data/lib/grape/validations/default.rb +25 -0
  64. data/lib/grape/validations/presence.rb +7 -3
  65. data/lib/grape/validations/regexp.rb +2 -5
  66. data/lib/grape/validations/values.rb +17 -0
  67. data/lib/grape/validations.rb +161 -54
  68. data/lib/grape/version.rb +1 -1
  69. data/lib/grape.rb +19 -4
  70. data/spec/grape/api_spec.rb +897 -268
  71. data/spec/grape/endpoint_spec.rb +283 -66
  72. data/spec/grape/entity_spec.rb +132 -29
  73. data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
  74. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  75. data/spec/grape/middleware/auth/basic_spec.rb +8 -8
  76. data/spec/grape/middleware/auth/digest_spec.rb +5 -5
  77. data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
  78. data/spec/grape/middleware/base_spec.rb +8 -13
  79. data/spec/grape/middleware/error_spec.rb +13 -17
  80. data/spec/grape/middleware/exception_spec.rb +47 -27
  81. data/spec/grape/middleware/formatter_spec.rb +103 -41
  82. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  83. data/spec/grape/middleware/versioner/header_spec.rb +76 -51
  84. data/spec/grape/middleware/versioner/param_spec.rb +18 -18
  85. data/spec/grape/middleware/versioner/path_spec.rb +6 -6
  86. data/spec/grape/middleware/versioner_spec.rb +5 -2
  87. data/spec/grape/path_spec.rb +229 -0
  88. data/spec/grape/util/hash_stack_spec.rb +31 -32
  89. data/spec/grape/validations/coerce_spec.rb +116 -51
  90. data/spec/grape/validations/default_spec.rb +123 -0
  91. data/spec/grape/validations/presence_spec.rb +42 -44
  92. data/spec/grape/validations/regexp_spec.rb +9 -9
  93. data/spec/grape/validations/values_spec.rb +138 -0
  94. data/spec/grape/validations/zh-CN.yml +4 -3
  95. data/spec/grape/validations_spec.rb +681 -48
  96. data/spec/shared/versioning_examples.rb +22 -6
  97. data/spec/spec_helper.rb +3 -2
  98. data/spec/support/basic_auth_encode_helpers.rb +0 -1
  99. data/spec/support/content_type_helpers.rb +11 -0
  100. data/spec/support/versioned_helpers.rb +13 -5
  101. metadata +34 -84
data/README.md CHANGED
@@ -8,16 +8,15 @@ providing a simple DSL to easily develop RESTful APIs. It has built-in support
8
8
  for common conventions, including multiple formats, subdomain/prefix restriction,
9
9
  content negotiation, versioning and much more.
10
10
 
11
- [![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape)
11
+ [![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape) [![Code Climate](https://codeclimate.com/github/intridea/grape.png)](https://codeclimate.com/github/intridea/grape) [![Inline docs](http://inch-pages.github.io/github/intridea/grape.png)](http://inch-pages.github.io/github/intridea/grape) [![Dependency Status](https://www.versioneye.com/ruby/grape/0.6.1/badge.png)](https://www.versioneye.com/ruby/grape/0.6.1)
12
12
 
13
13
  ## Stable Release
14
14
 
15
- You're reading the documentation for the next release of Grape, which should be 0.3.
16
- The current stable release is [0.2.6](https://github.com/intridea/grape/blob/v0.2.6/README.markdown).
15
+ You're reading the documentation for the stable release of Grape, 0.7.0.
17
16
 
18
- ## Project Tracking
17
+ ## Project Resources
19
18
 
20
- * [Grape Google Group](http://groups.google.com/group/ruby-grape)
19
+ * Need help? [Grape Google Group](http://groups.google.com/group/ruby-grape)
21
20
  * [Grape Wiki](https://github.com/intridea/grape/wiki)
22
21
 
23
22
  ## Installation
@@ -41,8 +40,7 @@ the context of recreating parts of the Twitter API.
41
40
  ```ruby
42
41
  module Twitter
43
42
  class API < Grape::API
44
-
45
- version 'v1', :using => :header, :vendor => 'twitter'
43
+ version 'v1', using: :header, vendor: 'twitter'
46
44
  format :json
47
45
 
48
46
  helpers do
@@ -56,7 +54,6 @@ module Twitter
56
54
  end
57
55
 
58
56
  resource :statuses do
59
-
60
57
  desc "Return a public timeline."
61
58
  get :public_timeline do
62
59
  Status.limit(20)
@@ -70,46 +67,47 @@ module Twitter
70
67
 
71
68
  desc "Return a status."
72
69
  params do
73
- requires :id, :type => Integer, :desc => "Status id."
70
+ requires :id, type: Integer, desc: "Status id."
74
71
  end
75
- get ':id' do
76
- Status.find(params[:id])
72
+ route_param :id do
73
+ get do
74
+ Status.find(params[:id])
75
+ end
77
76
  end
78
77
 
79
78
  desc "Create a status."
80
79
  params do
81
- requires :status, :type => String, :desc => "Your status."
80
+ requires :status, type: String, desc: "Your status."
82
81
  end
83
82
  post do
84
83
  authenticate!
85
84
  Status.create!({
86
- :user => current_user,
87
- :text => params[:status]
85
+ user: current_user,
86
+ text: params[:status]
88
87
  })
89
88
  end
90
89
 
91
90
  desc "Update a status."
92
91
  params do
93
- requires :id, :type => String, :desc => "Status ID."
94
- requires :status, :type => String, :desc => "Your status."
92
+ requires :id, type: String, desc: "Status ID."
93
+ requires :status, type: String, desc: "Your status."
95
94
  end
96
95
  put ':id' do
97
96
  authenticate!
98
97
  current_user.statuses.find(params[:id]).update({
99
- :user => current_user,
100
- :text => params[:status]
98
+ user: current_user,
99
+ text: params[:status]
101
100
  })
102
101
  end
103
102
 
104
103
  desc "Delete a status."
105
104
  params do
106
- requires :id, :type => String, :desc => "Status ID."
105
+ requires :id, type: String, desc: "Status ID."
107
106
  end
108
107
  delete ':id' do
109
108
  authenticate!
110
109
  current_user.statuses.find(params[:id]).destroy
111
110
  end
112
-
113
111
  end
114
112
  end
115
113
  end
@@ -137,13 +135,40 @@ And would respond to the following routes:
137
135
 
138
136
  Grape will also automatically respond to HEAD and OPTIONS for all GET, and just OPTIONS for all other routes.
139
137
 
138
+ ### Alongside Sinatra (or other frameworks)
139
+
140
+ If you wish to mount Grape alongside another Rack framework such as Sinatra, you can do so easily using
141
+ `Rack::Cascade`:
142
+
143
+ ```ruby
144
+ # Example config.ru
145
+
146
+ require 'sinatra'
147
+ require 'grape'
148
+
149
+ class API < Grape::API
150
+ get :hello do
151
+ { hello: "world" }
152
+ end
153
+ end
154
+
155
+ class Web < Sinatra::Base
156
+ get '/' do
157
+ "Hello world."
158
+ end
159
+ end
160
+
161
+ use Rack::Session::Cookie
162
+ run Rack::Cascade.new [API, Web]
163
+ ```
164
+
140
165
  ### Rails
141
166
 
142
167
  Place API files into `app/api` and modify `application.rb`.
143
168
 
144
169
  ```ruby
145
- config.paths.add "app/api", :glob => "**/*.rb"
146
- config.autoload_paths += Dir["#{Rails.root}/app/api/*"]
170
+ config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
171
+ config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
147
172
  ```
148
173
 
149
174
  Modify `config/routes`:
@@ -176,13 +201,13 @@ end
176
201
 
177
202
  ## Versioning
178
203
 
179
- There are three strategies in which clients can reach your API's endpoints: `:path`,
180
- `:header` and `:param`. The default strategy is `:path`.
204
+ There are four strategies in which clients can reach your API's endpoints: `:path`,
205
+ `:header`, `:accept_version_header` and `:param`. The default strategy is `:path`.
181
206
 
182
207
  ### Path
183
208
 
184
209
  ```ruby
185
- version 'v1', :using => :path
210
+ version 'v1', using: :path
186
211
  ```
187
212
 
188
213
  Using this versioning strategy, clients should pass the desired version in the URL.
@@ -192,22 +217,41 @@ Using this versioning strategy, clients should pass the desired version in the U
192
217
  ### Header
193
218
 
194
219
  ```ruby
195
- version 'v1', :using => :header, :vendor => 'twitter'
220
+ version 'v1', using: :header, vendor: 'twitter'
196
221
  ```
197
222
 
198
223
  Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.
199
224
 
200
- curl -H Accept=application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
225
+ curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
201
226
 
202
227
  By default, the first matching version is used when no `Accept` header is
203
228
  supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
204
229
  one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
205
230
  is returned when no correct `Accept` header is supplied.
206
231
 
232
+ When an invalid `Accept` header is supplied, a `406 Not Acceptable` error is returned if the `:cascade`
233
+ option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route
234
+ matches.
235
+
236
+ ### Accept-Version Header
237
+
238
+ ```ruby
239
+ version 'v1', using: :accept_version_header
240
+ ```
241
+
242
+ Using this versioning strategy, clients should pass the desired version in the HTTP `Accept-Version` header.
243
+
244
+ curl -H "Accept-Version:v1" http://localhost:9292/statuses/public_timeline
245
+
246
+ By default, the first matching version is used when no `Accept-Version` header is
247
+ supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
248
+ one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
249
+ is returned when no correct `Accept` header is supplied.
250
+
207
251
  ### Param
208
252
 
209
253
  ```ruby
210
- version 'v1', :using => :param
254
+ version 'v1', using: :param
211
255
  ```
212
256
 
213
257
  Using this versioning strategy, clients should pass the desired version as a request parameter,
@@ -218,7 +262,7 @@ either in the URL query string or in the request body.
218
262
  The default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.
219
263
 
220
264
  ```ruby
221
- version 'v1', :using => :param, :parameter => "v"
265
+ version 'v1', using: :param, parameter: "v"
222
266
  ```
223
267
 
224
268
  curl -H http://localhost:9292/statuses/public_timeline?v=v1
@@ -246,7 +290,7 @@ get :public_timeline do
246
290
  end
247
291
  ```
248
292
 
249
- Parameters are automatically populated from the request body on POST and PUT for form input, JSON and
293
+ Parameters are automatically populated from the request body on `POST` and `PUT` for form input, JSON and
250
294
  XML content-types.
251
295
 
252
296
  The request:
@@ -259,7 +303,7 @@ The Grape endpoint:
259
303
 
260
304
  ```ruby
261
305
  post '/statuses' do
262
- Status.create!({ :text => params[:text] })
306
+ Status.create!(text: params[:text])
263
307
  end
264
308
  ```
265
309
 
@@ -268,7 +312,7 @@ Multipart POSTs and PUTs are supported as well.
268
312
  The request:
269
313
 
270
314
  ```
271
- curl --form image_file=image.jpg http://localhost:9292/upload
315
+ curl --form image_file=@image.jpg http://localhost:9292/upload
272
316
  ```
273
317
 
274
318
  The Grape endpoint:
@@ -279,6 +323,14 @@ post "upload" do
279
323
  end
280
324
  ```
281
325
 
326
+ In the case of conflict between either of:
327
+
328
+ * route string parameters
329
+ * `GET`, `POST` and `PUT` parameters
330
+ * the contents of the request body on `POST` and `PUT`
331
+
332
+ route string parameters will have precedence.
333
+
282
334
  ## Parameter Validation and Coercion
283
335
 
284
336
  You can define validations and coercion options for your parameters using a `params` block.
@@ -290,6 +342,9 @@ params do
290
342
  group :media do
291
343
  requires :url
292
344
  end
345
+ optional :audio do
346
+ requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg], default: :mp3
347
+ end
293
348
  end
294
349
  put ':id' do
295
350
  # params[:id] is an Integer
@@ -299,8 +354,57 @@ end
299
354
  When a type is specified an implicit validation is done after the coercion to ensure
300
355
  the output type is the one declared.
301
356
 
302
- Parameters can be nested using `group`. In the above example, this means
303
- `params[:media][:url]` is required along with `params[:id]`.
357
+ Optional parameters can have a default value.
358
+
359
+ ```ruby
360
+ params do
361
+ optional :color, type: String, default: 'blue'
362
+ optional :random_number, type: Integer, default: -> { Random.rand(1..100) }
363
+ optional :non_random_number, type: Integer, default: Random.rand(1..100)
364
+ end
365
+ ```
366
+
367
+ Parameters can be restricted to a specific set of values with the `:values` option.
368
+
369
+ Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same
370
+ number for each call to the endpoint of this `params` block. To have the default evaluate
371
+ at calltime use a lambda, like `:random_number` above.
372
+
373
+ ```ruby
374
+ params do
375
+ requires :status, type: Symbol, values: [:not_started, :processing, :done]
376
+ end
377
+ ```
378
+
379
+ The :values option can also be supplied with a `Proc` to be evalutated at runtime. For example, given a status
380
+ model you may want to restrict by hashtags that you have previously defined in the `HashTag` model.
381
+
382
+ ```ruby
383
+ params do
384
+ required :hashtag, type: String, values: -> { Hashtag.all.map(&:tag) }
385
+ end
386
+ ```
387
+
388
+ Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
389
+ In the above example, this means `params[:media][:url]` is required along with `params[:id]`,
390
+ and `params[:audio][:format]` is required only if `params[:audio]` is present.
391
+ With a block, `group`, `requires` and `optional` accept an additional option `type` which can
392
+ be either `Array` or `Hash`, and defaults to `Array`. Depending on the value, the nested
393
+ parameters will be treated either as values of a hash or as values of hashes in an array.
394
+
395
+ ```ruby
396
+ params do
397
+ optional :preferences, type: Array do
398
+ requires :key
399
+ requires :value
400
+ end
401
+
402
+ requires :name, type: Hash do
403
+ requires :first_name
404
+ requires :last_name
405
+ end
406
+ end
407
+ ```
304
408
 
305
409
  ### Namespace Validation and Coercion
306
410
 
@@ -326,13 +430,30 @@ end
326
430
  The `namespace` method has a number of aliases, including: `group`, `resource`,
327
431
  `resources`, and `segment`. Use whichever reads the best for your API.
328
432
 
433
+ You can conveniently define a route parameter as a namespace using `route_param`.
434
+
435
+ ```ruby
436
+ namespace :statuses do
437
+ route_param :id do
438
+ desc "Returns all replies for a status."
439
+ get 'replies' do
440
+ Status.find(params[:id]).replies
441
+ end
442
+ desc "Returns a status."
443
+ get do
444
+ Status.find(params[:id])
445
+ end
446
+ end
447
+ end
448
+ ```
449
+
329
450
  ### Custom Validators
330
451
 
331
452
  ```ruby
332
453
  class AlphaNumeric < Grape::Validations::Validator
333
454
  def validate_param!(attr_name, params)
334
455
  unless params[attr_name] =~ /^[[:alnum:]]+$/
335
- throw :error, :status => 400, :message => "#{attr_name}: must consist of alpha-numeric characters"
456
+ raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message: "must consist of alpha-numeric characters"
336
457
  end
337
458
  end
338
459
  end
@@ -340,7 +461,7 @@ end
340
461
 
341
462
  ```ruby
342
463
  params do
343
- requires :text, :alpha_numeric => true
464
+ requires :text, alpha_numeric: true
344
465
  end
345
466
  ```
346
467
 
@@ -350,7 +471,7 @@ You can also create custom classes that take parameters.
350
471
  class Length < Grape::Validations::SingleOptionValidator
351
472
  def validate_param!(attr_name, params)
352
473
  unless params[attr_name].length <= @option
353
- throw :error, :status => 400, :message => "#{attr_name}: must be at the most #{@option} characters long"
474
+ raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message: "must be at the most #{@option} characters long"
354
475
  end
355
476
  end
356
477
  end
@@ -358,26 +479,33 @@ end
358
479
 
359
480
  ```ruby
360
481
  params do
361
- requires :text, :length => 140
482
+ requires :text, length: 140
362
483
  end
363
484
  ```
364
485
 
365
486
  ### Validation Errors
366
487
 
367
- When validation and coercion errors occur an exception of type `Grape::Exceptions::ValidationError` is raised.
488
+ Validation and coercion errors are collected and an exception of type `Grape::Exceptions::ValidationErrors` is raised.
368
489
  If the exception goes uncaught it will respond with a status of 400 and an error message.
369
- You can rescue a `Grape::Exceptions::ValidationError` and respond with a custom response.
490
+ You can rescue a `Grape::Exceptions::ValidationErrors` and respond with a custom response.
370
491
 
371
492
  ```ruby
372
- rescue_from Grape::Exceptions::ValidationError do |e|
493
+ rescue_from Grape::Exceptions::ValidationErrors do |e|
373
494
  Rack::Response.new({
374
- 'status' => e.status,
375
- 'message' => e.message,
376
- 'param' => e.param
495
+ status: e.status,
496
+ message: e.message,
497
+ errors: e.errors
377
498
  }.to_json, e.status)
378
499
  end
379
500
  ```
380
501
 
502
+ The validation errors are grouped by parameter name and can be accessed via ``Grape::Exceptions::ValidationErrors#errors``.
503
+
504
+ ### I18n
505
+
506
+ Grape supports I18n for parameter-related error messages, but will fallback to English if
507
+ translations for the default locale have not been provided. See [en.yml](lib/grape/locale/en.yml) for message keys.
508
+
381
509
  ## Headers
382
510
 
383
511
  Request headers are available through the `headers` helper or from `env` in their original form.
@@ -397,18 +525,32 @@ end
397
525
  You can set a response header with `header` inside an API.
398
526
 
399
527
  ```ruby
400
- header "X-Robots-Tag", "noindex"
528
+ header 'X-Robots-Tag', 'noindex'
529
+ ```
530
+
531
+ When raising `error!`, pass additional headers as arguments.
532
+
533
+ ```ruby
534
+ error! 'Unauthorized', 401, 'X-Error-Detail' => 'Invalid token.'
401
535
  ```
402
536
 
403
537
  ## Routes
404
538
 
405
539
  Optionally, you can define requirements for your named route parameters using regular
406
- expressions. The route will match only if all requirements are met.
540
+ expressions on namespace or endpoint. The route will match only if all requirements are met.
407
541
 
408
542
  ```ruby
409
- get ':id', :requirements => { :id => /[0-9]*/ } do
543
+ get ':id', requirements: { id: /[0-9]*/ } do
410
544
  Status.find(params[:id])
411
545
  end
546
+
547
+ namespace :outer, requirements: { id: /[0-9]*/ } do
548
+ get :id do
549
+ end
550
+
551
+ get ":id/edit" do
552
+ end
553
+ end
412
554
  ```
413
555
 
414
556
  ## Helpers
@@ -441,23 +583,84 @@ class API < Grape::API
441
583
  end
442
584
  ```
443
585
 
586
+ You can define reusable `params` using `helpers`.
587
+
588
+ ```ruby
589
+ class API < Grape::API
590
+ helpers do
591
+ params :pagination do
592
+ optional :page, type: Integer
593
+ optional :per_page, type: Integer
594
+ end
595
+ end
596
+
597
+ desc "Get collection"
598
+ params do
599
+ use :pagination # aliases: includes, use_scope
600
+ end
601
+ get do
602
+ Collection.page(params[:page]).per(params[:per_page])
603
+ end
604
+ end
605
+ ```
606
+
607
+ You can also define reusable `params` using shared helpers.
608
+
609
+ ```ruby
610
+ module SharedParams
611
+ extend Grape::API::Helpers
612
+
613
+ params :period do
614
+ optional :start_date
615
+ optional :end_date
616
+ end
617
+
618
+ params :pagination do
619
+ optional :page, type: Integer
620
+ optional :per_page, type: Integer
621
+ end
622
+ end
623
+
624
+ class API < Grape::API
625
+ helpers SharedParams
626
+
627
+ desc "Get collection"
628
+ params do
629
+ use :period, :pagination
630
+ end
631
+ get do
632
+ Collection.from(params[:start_date]).to(params[:end_date])
633
+ .page(params[:page]).per(params[:per_page])
634
+ end
635
+ end
636
+ ```
637
+
638
+ ## Parameter Documentation
639
+
640
+ You can attach additional documentation to `params` using a `documentation` hash.
641
+
642
+ ```ruby
643
+ params do
644
+ optional :first_name, type: String, documentation: { example: 'Jim' }
645
+ requires :last_name, type: String, documentation: { example: 'Smith' }
646
+ end
647
+ ```
648
+
444
649
  ## Cookies
445
650
 
446
651
  You can set, get and delete your cookies very simply using `cookies` method.
447
652
 
448
653
  ```ruby
449
654
  class API < Grape::API
450
-
451
655
  get 'status_count' do
452
656
  cookies[:status_count] ||= 0
453
657
  cookies[:status_count] += 1
454
- { :status_count => cookies[:status_count] }
658
+ { status_count: cookies[:status_count] }
455
659
  end
456
660
 
457
661
  delete 'status_count' do
458
- { :status_count => cookies.delete(:status_count) }
662
+ { status_count: cookies.delete(:status_count) }
459
663
  end
460
-
461
664
  end
462
665
  ```
463
666
 
@@ -465,10 +668,10 @@ Use a hash-based syntax to set more than one value.
465
668
 
466
669
  ```ruby
467
670
  cookies[:status_count] = {
468
- :value => 0,
469
- :expires => Time.tomorrow,
470
- :domain => '.twitter.com',
471
- :path => '/'
671
+ value: 0,
672
+ expires: Time.tomorrow,
673
+ domain: '.twitter.com',
674
+ path: '/'
472
675
  }
473
676
 
474
677
  cookies[:status_count][:value] +=1
@@ -483,7 +686,7 @@ cookies.delete :status_count
483
686
  Specify an optional path.
484
687
 
485
688
  ```ruby
486
- cookies.delete :status_count, :path => '/'
689
+ cookies.delete :status_count, path: '/'
487
690
  ```
488
691
 
489
692
  ## Redirecting
@@ -491,11 +694,11 @@ cookies.delete :status_count, :path => '/'
491
694
  You can redirect to a new url temporarily (302) or permanently (301).
492
695
 
493
696
  ```ruby
494
- redirect "/statuses"
697
+ redirect '/statuses'
495
698
  ```
496
699
 
497
700
  ```ruby
498
- redirect "/statuses", :permanent => true
701
+ redirect '/statuses', permanent: true
499
702
  ```
500
703
 
501
704
  ## Allowed Methods
@@ -506,13 +709,11 @@ behavior with `do_not_route_head!`.
506
709
 
507
710
  ``` ruby
508
711
  class API < Grape::API
509
-
510
712
  do_not_route_head!
511
713
 
512
714
  get '/example' do
513
715
  # only responds to GET
514
716
  end
515
-
516
717
  end
517
718
  ```
518
719
 
@@ -522,19 +723,17 @@ include an "Allow" header listing the supported methods.
522
723
 
523
724
  ```ruby
524
725
  class API < Grape::API
525
-
526
726
  get '/rt_count' do
527
- { :rt_count => current_user.rt_count }
727
+ { rt_count: current_user.rt_count }
528
728
  end
529
729
 
530
730
  params do
531
- requires :value, :type => Integer, :desc => 'Value to add to the rt count.'
731
+ requires :value, type: Integer, desc: 'Value to add to the rt count.'
532
732
  end
533
733
  put '/rt_count' do
534
734
  current_user.rt_count += params[:value].to_i
535
- { :rt_count => current_user.rt_count }
735
+ { rt_count: current_user.rt_count }
536
736
  end
537
-
538
737
  end
539
738
  ```
540
739
 
@@ -567,16 +766,43 @@ curl -X DELETE -v http://localhost:3000/rt_count/
567
766
  You can abort the execution of an API method by raising errors with `error!`.
568
767
 
569
768
  ```ruby
570
- error! "Access Denied", 401
769
+ error! 'Access Denied', 401
571
770
  ```
572
771
 
573
772
  You can also return JSON formatted objects by raising error! and passing a hash
574
773
  instead of a message.
575
774
 
576
775
  ```ruby
577
- error! { "error" => "unexpected error", "detail" => "missing widget" }, 500
776
+ error!({ error: "unexpected error", detail: "missing widget" }, 500)
578
777
  ```
579
778
 
779
+ ### Default Error HTTP Status Code
780
+
781
+ By default Grape returns a 500 status code from `error!`. You can change this with `default_error_status`.
782
+
783
+ ``` ruby
784
+ class API < Grape::API
785
+ default_error_status 400
786
+ get '/example' do
787
+ error! "This should have http status code 400"
788
+ end
789
+ end
790
+ ```
791
+
792
+ ### Handling 404
793
+
794
+ For Grape to handle all the 404s for your API, it can be useful to use a catch-all.
795
+ In its simplest form, it can be like:
796
+
797
+ ```ruby
798
+ route :any, '*path' do
799
+ error! # or something else
800
+ end
801
+ ```
802
+
803
+ It is very crucial to __define this endpoint at the very end of your API__, as it
804
+ literally accepts every request.
805
+
580
806
  ## Exception Handling
581
807
 
582
808
  Grape can be told to rescue all exceptions and return them in the API format.
@@ -591,10 +817,12 @@ You can also rescue specific exceptions.
591
817
 
592
818
  ```ruby
593
819
  class Twitter::API < Grape::API
594
- rescue_from ArgumentError, NotImplementedError
820
+ rescue_from ArgumentError, UserDefinedError
595
821
  end
596
822
  ```
597
823
 
824
+ In this case ```UserDefinedError``` must be inherited from ```StandardError```.
825
+
598
826
  The error format will match the request format. See "Content-Types" below.
599
827
 
600
828
  Custom error formatters for existing and additional types can be defined with a proc.
@@ -621,13 +849,13 @@ class Twitter::API < Grape::API
621
849
  end
622
850
  ```
623
851
 
624
- You can rescue all exceptions with a code block. The `rack_response` wrapper
852
+ You can rescue all exceptions with a code block. The `error_response` wrapper
625
853
  automatically sets the default error code and content-type.
626
854
 
627
855
  ```ruby
628
856
  class Twitter::API < Grape::API
629
857
  rescue_from :all do |e|
630
- rack_response({ :message => "rescued from #{e.class.name}" })
858
+ error_response({ message: "rescued from #{e.class.name}" })
631
859
  end
632
860
  end
633
861
  ```
@@ -648,29 +876,68 @@ Or rescue specific exceptions.
648
876
  ```ruby
649
877
  class Twitter::API < Grape::API
650
878
  rescue_from ArgumentError do |e|
651
- Rack::Response.new([ "ArgumentError: #{e.message}" ], 500)
879
+ Rack::Response.new([ "ArgumentError: #{e.message}" ], 500).finish
652
880
  end
653
881
  rescue_from NotImplementedError do |e|
654
- Rack::Response.new([ "NotImplementedError: #{e.message}" ], 500)
882
+ Rack::Response.new([ "NotImplementedError: #{e.message}" ], 500).finish
655
883
  end
656
884
  end
657
885
  ```
658
886
 
887
+ By default, `rescue_from` will rescue the exceptions listed and all their subclasses.
888
+
889
+ Assume you have the following exception classes defined.
890
+
891
+ ```ruby
892
+ module APIErrors
893
+ class ParentError < StandardError; end
894
+ class ChildError < ParentError; end
895
+ end
896
+ ```
897
+
898
+ Then the following `rescue_from` clause will rescue exceptions of type `APIErrors::ParentError` and its subclasses (in this case `APIErrors::ChildError`).
899
+
900
+ ```ruby
901
+ rescue_from APIErrors::ParentError do |e|
902
+ Rack::Response.new({
903
+ error: "#{e.class} error",
904
+ message: e.message
905
+ }.to_json, e.status).finish
906
+ end
907
+ ```
908
+
909
+ To only rescue the base exception class, set `rescue_subclasses: false`.
910
+ The code below will rescue exceptions of type `RuntimeError` but _not_ its subclasses.
911
+
912
+ ```ruby
913
+ rescue_from RuntimeError, rescue_subclasses: false do |e|
914
+ Rack::Response.new(
915
+ status: e.status,
916
+ message: e.message,
917
+ errors: e.errors
918
+ }.to_json, e.status).finish
919
+ end
920
+ ```
921
+
659
922
  #### Rails 3.x
660
923
 
661
- When mounted inside Rails 3.x, errors like "404 Not Found" or "406 Not Acceptable" will likely be
662
- handled and rendered by Rails handlers. For instance, accessing a nonexistent route "/api/foo"
663
- raises a 404, which inside rails will ultimately be translated to an `ActionController::RoutingError`,
664
- which most likely will get rendered to a HTML error page.
924
+ When mounted inside containers, such as Rails 3.x, errors like "404 Not Found" or
925
+ "406 Not Acceptable" will likely be handled and rendered by Rails handlers. For instance,
926
+ accessing a nonexistent route "/api/foo" raises a 404, which inside rails will ultimately
927
+ be translated to an `ActionController::RoutingError`, which most likely will get rendered
928
+ to a HTML error page.
665
929
 
666
- Most APIs will enjoy avoiding Rails exceptions and have their own exceptions reaching the client.
667
- In that case, the `:cascade` option can be set to `false` on the versioning definition.
930
+ Most APIs will enjoy preventing downstream handlers from handling errors. You may set the
931
+ `:cascade` option to `false` for the entire API or separately on specific `version` definitions,
932
+ which will remove the `X-Cascade: true` header from API responses.
668
933
 
669
934
  ```ruby
670
- version 'v1', :using => :header, :vendor => 'twitter', :cascade => false
935
+ cascade false
671
936
  ```
672
937
 
673
- The `:cascade` option can also be used with the other versioning strategies (`:param` and `:path`).
938
+ ```ruby
939
+ version 'v1', using: :header, vendor: 'twitter', cascade: false
940
+ ```
674
941
 
675
942
  ## Logging
676
943
 
@@ -722,9 +989,8 @@ By default, Grape supports _XML_, _JSON_, and _TXT_ content-types. The default f
722
989
 
723
990
  Serialization takes place automatically. For example, you do not have to call `to_json` in each JSON API implementation.
724
991
 
725
- Your API can declare which types to support by using `content_type`. Response format
726
- is determined by the request's extension, an explicit `format` parameter in the query
727
- string, or `Accept` header.
992
+ Your API can declare which types to support by using `content_type`. Response format is determined by the
993
+ request's extension, an explicit `format` parameter in the query string, or `Accept` header.
728
994
 
729
995
  The following API will only respond to the JSON content-type and will not parse any other input than `application/json`,
730
996
  `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`. All other requests
@@ -736,6 +1002,16 @@ class Twitter::API < Grape::API
736
1002
  end
737
1003
  ```
738
1004
 
1005
+ When the content-type is omitted, Grape will return a 406 error code unless `default_format` is specified.
1006
+ The following API will try to parse any data without a content-type using a JSON parser.
1007
+
1008
+ ```ruby
1009
+ class Twitter::API < Grape::API
1010
+ format :json
1011
+ default_format :json
1012
+ end
1013
+ ```
1014
+
739
1015
  If you combine `format` with `rescue_from :all`, errors will be rendered using the same format.
740
1016
  If you do not want this behavior, set the default error formatter with `default_error_formatter`.
741
1017
 
@@ -773,7 +1049,7 @@ end
773
1049
 
774
1050
  Built-in formats are the following.
775
1051
 
776
- * `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump`
1052
+ * `:json` and `:jsonapi`: use object's `to_json` when available, otherwise call `MultiJson.dump`
777
1053
  * `:xml`: use object's `to_xml` when available, usually via `MultiXml`, otherwise call `to_s`
778
1054
  * `:txt`: use object's `to_txt` when available, otherwise `to_s`
779
1055
  * `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
@@ -796,6 +1072,43 @@ The order for choosing the format is the following.
796
1072
  * Use the default format, if specified by the `default_format` option.
797
1073
  * Default to `:txt`.
798
1074
 
1075
+ ### JSONP
1076
+
1077
+ Grape supports JSONP via [Rack::JSONP](https://github.com/rack/rack-contrib), part of the
1078
+ [rack-contrib](https://github.com/rack/rack-contrib) gem. Add `rack-contrib` to your `Gemfile`.
1079
+
1080
+ ```ruby
1081
+ require 'rack/contrib'
1082
+
1083
+ class API < Grape::API
1084
+ use Rack::JSONP
1085
+ format :json
1086
+ get '/' do
1087
+ 'Hello World'
1088
+ end
1089
+ end
1090
+ ```
1091
+
1092
+ ### CORS
1093
+
1094
+ Grape supports CORS via [Rack::CORS](https://github.com/cyu/rack-cors), part of the
1095
+ [rack-cors](https://github.com/cyu/rack-cors) gem. Add `rack-cors` to your `Gemfile`,
1096
+ then use the middleware in your config.ru file.
1097
+
1098
+ ```ruby
1099
+ require 'rack/cors'
1100
+
1101
+ use Rack::Cors do
1102
+ allow do
1103
+ origins '*'
1104
+ resource '*', headers: :any, methods: :get
1105
+ end
1106
+ end
1107
+
1108
+ run Twitter::API
1109
+
1110
+ ```
1111
+
799
1112
  ## Content-type
800
1113
 
801
1114
  Content-type is set by the formatter. You can override the content-type of the response at runtime
@@ -817,7 +1130,8 @@ section above. It also supports custom data formats. You must declare additional
817
1130
  `content_type` and optionally supply a parser via `parser` unless a parser is already available within
818
1131
  Grape to enable a custom format. Such a parser can be a function or a class.
819
1132
 
820
- Without a parser, data is available "as-is" and can be read with `env['rack.input'].read`.
1133
+ With a parser, parsed data is available "as-is" in `env['api.request.body']`.
1134
+ Without a parser, data is available "as-is" and in `env['api.request.input']`.
821
1135
 
822
1136
  The following example is a trivial parser that will assign any input with the "text/custom" content-type
823
1137
  to `:value`. The parameter will be available via `params[:value]` inside the API call.
@@ -825,7 +1139,7 @@ to `:value`. The parameter will be available via `params[:value]` inside the API
825
1139
  ```ruby
826
1140
  module CustomParser
827
1141
  def self.call(object, env)
828
- { :value => object.to_s }
1142
+ { value: object.to_s }
829
1143
  end
830
1144
  end
831
1145
  ```
@@ -846,6 +1160,8 @@ You can invoke the above API as follows.
846
1160
  curl -X PUT -d 'data' 'http://localhost:9292/value' -H Content-Type:text/custom -v
847
1161
  ```
848
1162
 
1163
+ You can disable parsing for a content-type with `nil`. For example, `parser :json, nil` will disable JSON parsing altogether. The request data is then available as-is in `env['api.request.body']`.
1164
+
849
1165
  ## RESTful Model Representations
850
1166
 
851
1167
  Grape supports a range of ways to present your data with some help from a generic `present` method,
@@ -854,23 +1170,22 @@ hash may include `:with`, which defines the entity to expose.
854
1170
 
855
1171
  ### Grape Entities
856
1172
 
857
- Add the [grape-entity](https://github.com/agileanimal/grape-entity) gem to your Gemfile.
858
- Please refer to the [grape-entity documentation](https://github.com/agileanimal/grape-entity/blob/master/README.markdown)
1173
+ Add the [grape-entity](https://github.com/intridea/grape-entity) gem to your Gemfile.
1174
+ Please refer to the [grape-entity documentation](https://github.com/intridea/grape-entity/blob/master/README.md)
859
1175
  for more details.
860
1176
 
861
1177
  The following example exposes statuses.
862
1178
 
863
1179
  ```ruby
864
1180
  module API
865
-
866
1181
  module Entities
867
1182
  class Status < Grape::Entity
868
1183
  expose :user_name
869
- expose :text, :documentation => { :type => "string", :desc => "Status update text." }
870
- expose :ip, :if => { :type => :full }
871
- expose :user_type, user_id, :if => lambda{ |status, options| status.user.public? }
1184
+ expose :text, documentation: { type: "string", desc: "Status update text." }
1185
+ expose :ip, if: { type: :full }
1186
+ expose :user_type, user_id, if: lambda { |status, options| status.user.public? }
872
1187
  expose :digest { |status, options| Digest::MD5.hexdigest(status.txt) }
873
- expose :replies, :using => API::Status, :as => :replies
1188
+ expose :replies, using: API::Status, as: :replies
874
1189
  end
875
1190
  end
876
1191
 
@@ -878,24 +1193,62 @@ module API
878
1193
  version 'v1'
879
1194
 
880
1195
  desc 'Statuses index', {
881
- :object_fields => API::Entities::Status.documentation
1196
+ params: API::Entities::Status.documentation
882
1197
  }
883
1198
  get '/statuses' do
884
1199
  statuses = Status.all
885
1200
  type = current_user.admin? ? :full : :default
886
- present statuses, with: API::Entities::Status, :type => type
1201
+ present statuses, with: API::Entities::Status, type: type
887
1202
  end
888
1203
  end
889
1204
  end
890
1205
  ```
891
1206
 
1207
+ You can use entity documentation directly in the params block with `using: Entity.documentation`.
1208
+
1209
+ ```ruby
1210
+ module API
1211
+ class Statuses < Grape::API
1212
+ version 'v1'
1213
+
1214
+ desc 'Create a status', {
1215
+ requires :all, except: [:ip], using: API::Entities::Status.documentation.except(:id)
1216
+ }
1217
+ post '/status' do
1218
+ Status.create! params
1219
+ end
1220
+ end
1221
+ end
1222
+ ```
1223
+
1224
+ You can present with multiple entities using an optional Symbol argument.
1225
+
1226
+ ```ruby
1227
+ get '/statuses' do
1228
+ statuses = Status.all.page(1).per(20)
1229
+ present :total_page, 10
1230
+ present :per_page, 20
1231
+ present :statuses, statuses, with: API::Entities::Status
1232
+ end
1233
+ ```
1234
+
1235
+ The response will be
1236
+
1237
+ ```
1238
+ {
1239
+ total_page: 10,
1240
+ per_page: 20,
1241
+ statuses: []
1242
+ }
1243
+ ```
1244
+
892
1245
  In addition to separately organizing entities, it may be useful to put them as namespaced
893
1246
  classes underneath the model they represent.
894
1247
 
895
1248
  ```ruby
896
1249
  class Status
897
1250
  def entity
898
- Status.new(self)
1251
+ Entity.new(self)
899
1252
  end
900
1253
 
901
1254
  class Entity < Grape::Entity
@@ -922,6 +1275,13 @@ You can use [Rabl](https://github.com/nesquena/rabl) templates with the help of
922
1275
  [grape-rabl](https://github.com/LTe/grape-rabl) gem, which defines a custom Grape Rabl
923
1276
  formatter.
924
1277
 
1278
+ ### Active Model Serializers
1279
+
1280
+ You can use [Active Model Serializers](https://github.com/rails-api/active_model_serializers) serializers with the help of the
1281
+ [grape-active_model_serializers](https://github.com/jrhe/grape-active_model_serializers) gem, which defines a custom Grape AMS
1282
+ formatter.
1283
+
1284
+
925
1285
  ## Authentication
926
1286
 
927
1287
  ### Basic and Digest Auth
@@ -936,7 +1296,7 @@ end
936
1296
  ```
937
1297
 
938
1298
  ```ruby
939
- http_digest({ :realm => 'Test Api', :opaque => 'app secret' }) do |username|
1299
+ http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
940
1300
  # lookup the user's password here
941
1301
  { 'user1' => 'password1' }[username]
942
1302
  end
@@ -971,7 +1331,7 @@ call with `route`.
971
1331
  class MyAPI < Grape::API
972
1332
  desc "Returns a description of a parameter."
973
1333
  params do
974
- requires :id, :type => Integer, :desc => "Identity."
1334
+ requires :id, type: Integer, desc: "Identity."
975
1335
  end
976
1336
  get "params/:id" do
977
1337
  route.route_params[params[:id]] # yields the parameter description
@@ -996,7 +1356,21 @@ end
996
1356
 
997
1357
  ## Before and After
998
1358
 
999
- Execute a block before or after every API call with `before` and `after`.
1359
+ Blocks can be executed before or after every API call, using `before`, `after`,
1360
+ `before_validation` and `after_validation`.
1361
+
1362
+ Before and after callbacks execute in the following order:
1363
+
1364
+ 1. `before`
1365
+ 2. `before_validation`
1366
+ 3. _validations_
1367
+ 4. `after_validation`
1368
+ 5. _the API call_
1369
+ 6. `after`
1370
+
1371
+ Steps 4, 5 and 6 only happen if validation succeeds.
1372
+
1373
+ E.g. using `before`:
1000
1374
 
1001
1375
  ```ruby
1002
1376
  before do
@@ -1004,6 +1378,68 @@ before do
1004
1378
  end
1005
1379
  ```
1006
1380
 
1381
+ The block applies to every API call within and below the current namespace:
1382
+
1383
+ ```ruby
1384
+ class MyAPI < Grape::API
1385
+ get '/' do
1386
+ "root - #{@blah}"
1387
+ end
1388
+
1389
+ namespace :foo do
1390
+ before do
1391
+ @blah = 'blah'
1392
+ end
1393
+
1394
+ get '/' do
1395
+ "root - foo - #{@blah}"
1396
+ end
1397
+
1398
+ namespace :bar do
1399
+ get '/' do
1400
+ "root - foo - bar - #{@blah}"
1401
+ end
1402
+ end
1403
+ end
1404
+ end
1405
+ ```
1406
+
1407
+ The behaviour is then:
1408
+
1409
+ ```bash
1410
+ GET / # 'root - '
1411
+ GET /foo # 'root - foo - blah'
1412
+ GET /foo/bar # 'root - foo - bar - blah'
1413
+ ```
1414
+
1415
+ Params on a `namespace` (or whatever alias you are using) also work when using
1416
+ `before_validation` or `after_validation`:
1417
+
1418
+ ```ruby
1419
+ class MyAPI < Grape::API
1420
+ params do
1421
+ requires :blah, type: Integer
1422
+ end
1423
+ resource ':blah' do
1424
+ after_validation do
1425
+ # if we reach this point validations will have passed
1426
+ @blah = declared(params, include_missing: false)[:blah]
1427
+ end
1428
+
1429
+ get '/' do
1430
+ @blah.class
1431
+ end
1432
+ end
1433
+ end
1434
+ ```
1435
+
1436
+ The behaviour is then:
1437
+
1438
+ ```bash
1439
+ GET /123 # 'Fixnum'
1440
+ GET /foo # 400 error - 'blah is invalid'
1441
+ ```
1442
+
1007
1443
  ## Anchoring
1008
1444
 
1009
1445
  Grape by default anchors all request paths, which means that the request URL
@@ -1011,7 +1447,7 @@ should match from start to end to match, otherwise a `404 Not Found` is
1011
1447
  returned. However, this is sometimes not what you want, because it is not always
1012
1448
  known upfront what can be expected from the call. This is because Rack-mount by
1013
1449
  default anchors requests to match from the start to the end, or not at all.
1014
- Rails solves this problem by using a `:anchor => false` option in your routes.
1450
+ Rails solves this problem by using a `anchor: false` option in your routes.
1015
1451
  In Grape this option can be used as well when a method is defined.
1016
1452
 
1017
1453
  For instance when you're API needs to get part of an URL, for instance:
@@ -1019,7 +1455,7 @@ For instance when you're API needs to get part of an URL, for instance:
1019
1455
  ```ruby
1020
1456
  class TwitterAPI < Grape::API
1021
1457
  namespace :statuses do
1022
- get '/(*:status)', :anchor => false do
1458
+ get '/(*:status)', anchor: false do
1023
1459
 
1024
1460
  end
1025
1461
  end
@@ -1093,13 +1529,13 @@ describe Twitter::API do
1093
1529
  end
1094
1530
  ```
1095
1531
 
1096
- In Rails, HTTP request tests would go into the `spec/request` group. You may want your API code to go into
1532
+ In Rails, HTTP request tests would go into the `spec/requests` group. You may want your API code to go into
1097
1533
  `app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
1098
1534
 
1099
1535
  ```ruby
1100
1536
  RSpec.configure do |config|
1101
- config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
1102
- :file_path => /spec\/api/
1537
+ config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
1538
+ file_path: /spec\/api/
1103
1539
  }
1104
1540
  end
1105
1541
  ```
@@ -1112,15 +1548,17 @@ Add API paths to `config/application.rb`.
1112
1548
 
1113
1549
  ```ruby
1114
1550
  # Auto-load API and its subdirectories
1115
- config.paths.add "app/api", :glob => "**/*.rb"
1116
- config.autoload_paths += Dir["#{Rails.root}/app/api/*"]
1551
+ config.paths.add File.join("app", "api"), glob: File.join("**", "*.rb")
1552
+ config.autoload_paths += Dir[Rails.root.join("app", "api", "*")]
1117
1553
  ```
1118
1554
 
1119
1555
  Create `config/initializers/reload_api.rb`.
1120
1556
 
1121
1557
  ```ruby
1122
1558
  if Rails.env.development?
1123
- api_files = Dir["#{Rails.root}/app/api/**/*.rb"]
1559
+ ActiveSupport::Dependencies.explicitly_unloadable_constants << "Twitter::API"
1560
+
1561
+ api_files = Dir[Rails.root.join('app', 'api', '**', '*.rb')]
1124
1562
  api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
1125
1563
  Rails.application.reload_routes!
1126
1564
  end
@@ -1132,21 +1570,18 @@ end
1132
1570
 
1133
1571
  See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-rails-3-reload-lib-directory-for-each-request/4368838#4368838) for more information.
1134
1572
 
1135
-
1136
1573
  ## Performance Monitoring
1137
1574
 
1138
- Grape integrates with NewRelic via the [newrelic-grape](https://github.com/flyerhzm/newrelic-grape) gem.
1575
+ Grape integrates with NewRelic via the
1576
+ [newrelic-grape](https://github.com/flyerhzm/newrelic-grape) gem, and
1577
+ with Librato Metrics with the [grape-librato](https://github.com/seanmoon/grape-librato) gem.
1139
1578
 
1140
1579
  ## Contributing to Grape
1141
1580
 
1142
- Grape is work of dozens of contributors. You're encouraged to submit pull requests, propose
1581
+ Grape is work of hundreds of contributors. You're encouraged to submit pull requests, propose
1143
1582
  features and discuss issues.
1144
1583
 
1145
- * Fork the project
1146
- * Write tests for your new feature or a test that reproduces a bug
1147
- * Implement your feature or make a bug fix
1148
- * Add a line to `CHANGELOG.markdown` describing your change
1149
- * Commit, push and make a pull request. Bonus points for topic branches.
1584
+ See [CONTRIBUTING](CONTRIBUTING.md).
1150
1585
 
1151
1586
  ## License
1152
1587
 
@@ -1154,4 +1589,4 @@ MIT License. See LICENSE for details.
1154
1589
 
1155
1590
  ## Copyright
1156
1591
 
1157
- Copyright (c) 2010-2012 Michael Bleigh, and Intridea, Inc.
1592
+ Copyright (c) 2010-2013 Michael Bleigh, and Intridea, Inc.