grape 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (40) hide show
  1. data/CHANGELOG.markdown +17 -3
  2. data/Gemfile +1 -1
  3. data/README.markdown +269 -171
  4. data/grape.gemspec +1 -0
  5. data/lib/grape.rb +43 -22
  6. data/lib/grape/api.rb +7 -4
  7. data/lib/grape/endpoint.rb +48 -9
  8. data/lib/grape/entity.rb +1 -1
  9. data/lib/grape/error_formatter/base.rb +32 -0
  10. data/lib/grape/error_formatter/json.rb +17 -0
  11. data/lib/grape/error_formatter/txt.rb +18 -0
  12. data/lib/grape/error_formatter/xml.rb +17 -0
  13. data/lib/grape/exceptions/validation_error.rb +9 -5
  14. data/lib/grape/formatter/base.rb +33 -0
  15. data/lib/grape/formatter/json.rb +15 -0
  16. data/lib/grape/formatter/serializable_hash.rb +35 -0
  17. data/lib/grape/formatter/txt.rb +13 -0
  18. data/lib/grape/formatter/xml.rb +13 -0
  19. data/lib/grape/middleware/base.rb +18 -103
  20. data/lib/grape/middleware/error.rb +16 -32
  21. data/lib/grape/middleware/formatter.rb +8 -9
  22. data/lib/grape/middleware/versioner.rb +3 -2
  23. data/lib/grape/middleware/versioner/header.rb +1 -1
  24. data/lib/grape/parser/base.rb +31 -0
  25. data/lib/grape/parser/json.rb +13 -0
  26. data/lib/grape/parser/xml.rb +13 -0
  27. data/lib/grape/validations.rb +1 -1
  28. data/lib/grape/validations/coerce.rb +1 -1
  29. data/lib/grape/validations/presence.rb +1 -1
  30. data/lib/grape/validations/regexp.rb +1 -1
  31. data/lib/grape/version.rb +1 -1
  32. data/spec/grape/api_spec.rb +183 -9
  33. data/spec/grape/endpoint_spec.rb +27 -1
  34. data/spec/grape/entity_spec.rb +21 -21
  35. data/spec/grape/middleware/base_spec.rb +15 -15
  36. data/spec/grape/middleware/exception_spec.rb +38 -16
  37. data/spec/grape/middleware/formatter_spec.rb +6 -40
  38. data/spec/grape/validations/presence_spec.rb +20 -20
  39. data/spec/spec_helper.rb +1 -1
  40. metadata +132 -58
@@ -1,5 +1,19 @@
1
- 0.2.2 (Next Release)
2
- ====================
1
+ 0.2.3 (24/12/2012)
2
+ ==================
3
+
4
+ * [#179](https://github.com/intridea/grape/issues/178): Using `content_type` will remove all default content-types - [@dblock](https://github.com/dblock).
5
+ * [#265](https://github.com/intridea/grape/issues/264): Fix: Moved `ValidationError` into `Grape::Exceptions` - [@thepumpkin1979](https://github.com/thepumpkin1979).
6
+ * [#269](https://github.com/intridea/grape/pull/269): Fix: `LocalJumpError` will not be raised when using explict return in API methods - [@simulacre](https://github.com/simulacre).
7
+ * [#86](https://github.com/intridea/grape/issues/275): Fix Path-based versioning not recognizing `/` route - [@walski](https://github.com/walski).
8
+ * [#273](https://github.com/intridea/grape/pull/273): Disabled formatting via `serializable_hash` and added support for `format :serializable_hash` - [@dblock](https://github.com/dblock).
9
+ * [#277](https://github.com/intridea/grape/pull/277): Added a DSL to declare `formatter` in API settings - [@tim-vandecasteele](https://github.com/tim-vandecasteele).
10
+ * [#284](https://github.com/intridea/grape/pull/284): Added a DSL to declare `error_formatter` in API settings - [@dblock](https://github.com/dblock).
11
+ * [#285](https://github.com/intridea/grape/pull/285): Removed `error_format` from API settings, now matches request format - [@dblock](https://github.com/dblock).
12
+ * [#290](https://github.com/intridea/grape/pull/290): The default error format for XML is now `error/message` instead of `hash/error` - [@dpsk](https://github.com/dpsk).
13
+ * [#44](https://github.com/intridea/grape/issues/44): Pass `env` into formatters to enable templating - [@dblock](https://github.com/dblock).
14
+
15
+ 0.2.2
16
+ =====
3
17
 
4
18
  Features
5
19
  --------
@@ -21,7 +35,7 @@ Fixes
21
35
  * [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
22
36
  * [#203](https://github.com/intridea/grape/pull/203): Added a check to `Entity#serializable_hash` that verifies an entity exists on an object - [@adamgotterer](https://github.com/adamgotterer).
23
37
  * [#208](https://github.com/intridea/grape/pull/208): `Entity#serializable_hash` must also check if attribute is generated by a user supplied block - [@ppadron](https://github.com/ppadron).
24
- * [#252](https://github.com/intridea/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) [@simulacre](https://github.com/simulacre)
38
+ * [#252](https://github.com/intridea/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) - [@simulacre](https://github.com/simulacre)
25
39
 
26
40
  0.2.1 (7/11/2012)
27
41
  =================
data/Gemfile CHANGED
@@ -10,6 +10,6 @@ group :development, :test do
10
10
  gem 'rb-fsevent'
11
11
  gem 'growl'
12
12
  gem 'json'
13
- gem 'rspec'
13
+ gem 'rspec'
14
14
  gem 'rack-test', "~> 0.6.2", :require => "rack/test"
15
15
  end
@@ -3,9 +3,9 @@
3
3
  ## What is Grape?
4
4
 
5
5
  Grape is a REST-like API micro-framework for Ruby. It's designed to run on Rack
6
- or complement existing web application frameworks such as Rails and Sinatra by
7
- providing a simple DSL to easily develop RESTful APIs. It has built-in support
8
- for common conventions, including multiple formats, subdomain/prefix restriction,
6
+ or complement existing web application frameworks such as Rails and Sinatra by
7
+ providing a simple DSL to easily develop RESTful APIs. It has built-in support
8
+ for common conventions, including multiple formats, subdomain/prefix restriction,
9
9
  content negotiation, versioning and much more.
10
10
 
11
11
  [![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape)
@@ -15,6 +15,11 @@ content negotiation, versioning and much more.
15
15
  * [Grape Google Group](http://groups.google.com/group/ruby-grape)
16
16
  * [Grape Wiki](https://github.com/intridea/grape/wiki)
17
17
 
18
+ ## Stable Release
19
+
20
+ You're reading the documentation for the next release of Grape, which should be 0.2.3.
21
+ The current stable release is [0.2.2](https://github.com/intridea/grape/blob/v0.2.2/README.markdown).
22
+
18
23
  ## Installation
19
24
 
20
25
  Grape is available as a gem, to install it just install the gem:
@@ -49,37 +54,60 @@ class Twitter::API < Grape::API
49
54
  end
50
55
 
51
56
  resource :statuses do
52
-
53
- desc "Returns a public timeline."
57
+
58
+ desc "Return a public timeline."
54
59
  get :public_timeline do
55
- Tweet.limit(20)
60
+ Status.limit(20)
56
61
  end
57
62
 
58
- desc "Returns a personal timeline."
63
+ desc "Return a personal timeline."
59
64
  get :home_timeline do
60
65
  authenticate!
61
- current_user.home_timeline
66
+ current_user.statuses.limit(20)
62
67
  end
63
68
 
64
- desc "Returns a tweet."
69
+ desc "Return a status."
65
70
  params do
66
- requires :id, :type => Integer, :desc => "Tweet id."
71
+ requires :id, :type => Integer, :desc => "Status id."
67
72
  end
68
- get '/show/:id' do
69
- Tweet.find(params[:id])
73
+ get ':id' do
74
+ Status.find(params[:id])
75
+ end
76
+
77
+ desc "Create a status."
78
+ params do
79
+ requires :status, :type => String, :desc => "Your status."
80
+ end
81
+ post do
82
+ authenticate!
83
+ Status.create!({
84
+ :user => current_user,
85
+ :text => params[:status]
86
+ })
70
87
  end
71
-
72
- desc "Creates a tweet."
88
+
89
+ desc "Update a status."
73
90
  params do
91
+ requires :id, :type => String, :desc => "Status ID."
74
92
  requires :status, :type => String, :desc => "Your status."
75
93
  end
76
- post :update do
94
+ put ':id' do
77
95
  authenticate!
78
- Tweet.create(
96
+ current_user.statuses.find(params[:id]).update({
79
97
  :user => current_user,
80
98
  :text => params[:status]
81
- )
99
+ })
100
+ end
101
+
102
+ desc "Delete a status."
103
+ params do
104
+ requires :id, :type => String, :desc => "Status ID."
105
+ end
106
+ delete ':id' do
107
+ authenticate!
108
+ current_user.statuses.find(params[:id]).destroy
82
109
  end
110
+
83
111
  end
84
112
 
85
113
  end
@@ -98,10 +126,12 @@ run Twitter::API
98
126
 
99
127
  And would respond to the following routes:
100
128
 
101
- GET /statuses/public_timeline(.json)
102
- GET /statuses/home_timeline(.json)
103
- GET /statuses/show/:id(.json)
104
- POST /statuses/update(.json)
129
+ GET /statuses/public_timeline(.json)
130
+ GET /statuses/home_timeline(.json)
131
+ GET /statuses/:id(.json)
132
+ POST /statuses(.json)
133
+ PUT /statuses/:id(.json)
134
+ DELETE /statuses/:id(.json)
105
135
 
106
136
  ### Rails
107
137
 
@@ -111,7 +141,7 @@ In a Rails application, modify *config/routes*:
111
141
  mount Twitter::API => "/"
112
142
  ```
113
143
 
114
- Note that you will need to restart Rails to pick up changes in your API classes
144
+ Note that when using Rails you will need to restart the server to pick up changes in your API classes
115
145
  (see [Issue 131](https://github.com/intridea/grape/issues/131)).
116
146
 
117
147
  ### Modules
@@ -128,7 +158,7 @@ end
128
158
 
129
159
  ## Versioning
130
160
 
131
- There are three strategies in which clients can reach your API's endpoints: `:header`,
161
+ There are three strategies in which clients can reach your API's endpoints: `:header`,
132
162
  `:path` and `:param`. The default strategy is `:path`.
133
163
 
134
164
  ### Header
@@ -162,10 +192,10 @@ Using this versioning strategy, clients should pass the desired version in the U
162
192
  version 'v1', :using => :param
163
193
  ```
164
194
 
165
- Using this versioning strategy, clients should pass the desired version as a request parameter,
195
+ Using this versioning strategy, clients should pass the desired version as a request parameter,
166
196
  either in the URL query string or in the request body.
167
197
 
168
- curl -H http://localhost:9292/events?apiver=v1
198
+ curl -H http://localhost:9292/statuses/public_timeline?apiver=v1
169
199
 
170
200
  The default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.
171
201
 
@@ -173,16 +203,16 @@ The default name for the query parameter is 'apiver' but can be specified using
173
203
  version 'v1', :using => :param, :parameter => "v"
174
204
  ```
175
205
 
176
- curl -H http://localhost:9292/events?v=v1
206
+ curl -H http://localhost:9292/statuses/public_timeline?v=v1
177
207
 
178
208
  ## Describing Methods
179
209
 
180
210
  You can add a description to API methods and namespaces.
181
211
 
182
212
  ``` ruby
183
- desc "Returns a reticulated spline."
184
- get "spline/:id" do
185
- Spline.find(params[:id])
213
+ desc "Returns your public timeline."
214
+ get :public_timeline do
215
+ Status.limit(20)
186
216
  end
187
217
  ```
188
218
 
@@ -192,8 +222,8 @@ Request parameters are available through the `params` hash object. This includes
192
222
  along with any named parameters you specify in your route strings.
193
223
 
194
224
  ```ruby
195
- get do
196
- Article.order(params[:sort_by])
225
+ get :public_timeline do
226
+ Status.order(params[:sort_by])
197
227
  end
198
228
  ```
199
229
 
@@ -201,13 +231,13 @@ Parameters are also populated from the request body on POST and PUT for JSON and
201
231
 
202
232
  The Request:
203
233
 
204
- ```curl -d '{"some_key": "some_value"}' 'http://localhost:9292/json_endpoint' -H Content-Type:application/json -v```
234
+ ```curl -d '{"text": "140 characters"}' 'http://localhost:9292/statuses' -H Content-Type:application/json -v```
205
235
 
206
236
  The Grape Endpoint:
207
237
 
208
238
  ```ruby
209
- post '/json_endpoint' do
210
- params[:some_key]
239
+ post '/statuses' do
240
+ Status.create!({ :text => params[:text] })
211
241
  end
212
242
  ```
213
243
 
@@ -218,59 +248,58 @@ You can define validations and coercion options for your parameters using a `par
218
248
  ```ruby
219
249
  params do
220
250
  requires :id, type: Integer
221
- optional :name, type: String, regexp: /^[a-z]+$/
222
-
223
- group :user do
224
- requires :first_name
225
- requires :last_name
251
+ optional :text, type: String, regexp: /^[a-z]+$/
252
+ group :media do
253
+ requires :url
226
254
  end
227
255
  end
228
- get ':id' do
256
+ put ':id' do
229
257
  # params[:id] is an Integer
230
258
  end
231
259
  ```
232
260
 
233
- When a type is specified an implicit validation is done after the coercion to ensure
261
+ When a type is specified an implicit validation is done after the coercion to ensure
234
262
  the output type is the one declared.
235
263
 
236
- Parameters can be nested using `group`. In the above example, this means both
237
- `params[:user][:first_name]` and `params[:user][:last_name]` are required next to `params[:id]`.
264
+ Parameters can be nested using `group`. In the above example, this means
265
+ `params[:media][:url]` is required along with `params[:id]`.
238
266
 
239
267
  ### Namespace Validation and Coercion
268
+
240
269
  Namespaces allow parameter definitions and apply to every method within the namespace.
241
270
 
242
271
  ```ruby
243
- namespace :shelves do
272
+ namespace :statuses do
244
273
  params do
245
- requires :shelf_id, type: Integer, desc: "A shelf."
274
+ requires :user_id, type: Integer, desc: "A user ID."
246
275
  end
247
- namespace ":shelf_id" do
248
- desc "Retrieve a book from a shelf."
276
+ namespace ":user_id" do
277
+ desc "Retrieve a user's status."
249
278
  params do
250
- requires :book_id, type: Integer, desc: "A book."
279
+ requires :status_id, type: Integer, desc: "A status ID."
251
280
  end
252
- get ":book_id" do
253
- # params[:shelf_id] defines a shelf
254
- # params[:book_id] defines a book
281
+ get ":status_id" do
282
+ User.find(params[:user_id]).statuses.find(params[:status_id])
255
283
  end
256
284
  end
257
285
  end
258
286
  ```
259
287
 
260
288
  ### Custom Validators
289
+
261
290
  ```ruby
262
291
  class AlphaNumeric < Grape::Validations::Validator
263
292
  def validate_param!(attr_name, params)
264
293
  unless params[attr_name] =~ /^[[:alnum:]]+$/
265
294
  throw :error, :status => 400, :message => "#{attr_name}: must consist of alpha-numeric characters"
266
- end
295
+ end
267
296
  end
268
- end
297
+ end
269
298
  ```
270
299
 
271
300
  ```ruby
272
301
  params do
273
- requires :username, :alpha_numeric => true
302
+ requires :text, :alpha_numeric => true
274
303
  end
275
304
  ```
276
305
 
@@ -279,33 +308,33 @@ You can also create custom classes that take parameters.
279
308
  ```ruby
280
309
  class Length < Grape::Validations::SingleOptionValidator
281
310
  def validate_param!(attr_name, params)
282
- unless params[attr_name].length == @option
283
- throw :error, :status => 400, :message => "#{attr_name}: must be #{@option} characters long"
284
- end
311
+ unless params[attr_name].length <= @option
312
+ throw :error, :status => 400, :message => "#{attr_name}: must be at the most #{@option} characters long"
313
+ end
285
314
  end
286
- end
287
- ```
315
+ end
316
+ ```
288
317
 
289
318
  ```ruby
290
319
  params do
291
- requires :name, :length => 5
320
+ requires :text, :length => 140
292
321
  end
293
322
  ```
294
323
 
295
324
  ### Validation Errors
296
325
 
297
- When validation and coercion erros occur an exception of type `ValidationError` is raised.
326
+ When validation and coercion errors occur an exception of type `Grape::Exceptions::ValidationError` is raised.
298
327
  If the exception goes uncaught it will respond with a status of 400 and an error message.
299
- You can rescue a `ValidationError` and respond with a custom response.
328
+ You can rescue a `Grape::Exceptions::ValidationError` and respond with a custom response.
300
329
 
301
330
  ```ruby
302
- rescue_from ValidationError do |e|
331
+ rescue_from Grape::Exceptions::ValidationError do |e|
303
332
  Rack::Response.new({
304
333
  'status' => e.status,
305
334
  'message' => e.message,
306
335
  'param' => e.param
307
- }.to_json, e.status)
308
- end
336
+ }.to_json, e.status)
337
+ end
309
338
  ```
310
339
 
311
340
  ## Headers
@@ -321,7 +350,7 @@ end
321
350
 
322
351
  ```ruby
323
352
  get do
324
- error! 'Unauthorized', 401 unless env['HTTP_SECRET_PASSWORD'] == 'swordfish'
353
+ error!('Unauthorized', 401) unless env['HTTP_SECRET_PASSWORD'] == 'swordfish'
325
354
  ...
326
355
  end
327
356
  ```
@@ -332,8 +361,8 @@ Optionally, you can define requirements for your named route parameters using re
332
361
  expressions. The route will match only if all requirements are met.
333
362
 
334
363
  ```ruby
335
- get '/show/:id', :requirements => { :id => /[0-9]*/ } do
336
- Tweet.find(params[:id])
364
+ get ':id', :requirements => { :id => /[0-9]*/ } do
365
+ Status.find(params[:id])
337
366
  end
338
367
  ```
339
368
 
@@ -343,9 +372,9 @@ You can define helper methods that your endpoints can use with the `helpers`
343
372
  macro by either giving a block or a module.
344
373
 
345
374
  ``` ruby
346
- module MyHelpers
347
- def say_hello(user)
348
- "hey there #{user.name}"
375
+ module StatusHelpers
376
+ def user_info(user)
377
+ "#{user} has statused #{user.statuses} status(s)"
349
378
  end
350
379
  end
351
380
 
@@ -358,11 +387,11 @@ class API < Grape::API
358
387
  end
359
388
 
360
389
  # or mix in a module
361
- helpers MyHelpers
390
+ helpers StatusHelpers
362
391
 
363
- get '/hello' do
392
+ get 'info' do
364
393
  # helpers available in your endpoint and filters
365
- say_hello(current_user)
394
+ user_info(current_user)
366
395
  end
367
396
  end
368
397
  ```
@@ -373,28 +402,31 @@ You can set, get and delete your cookies very simply using `cookies` method.
373
402
 
374
403
  ``` ruby
375
404
  class API < Grape::API
376
- get '/counter' do
377
- cookies[:counter] ||= 0
378
- cookies[:counter] += 1
379
- { :counter => cookies[:counter] }
405
+
406
+ get 'status_count' do
407
+ cookies[:status_count] ||= 0
408
+ cookies[:status_count] += 1
409
+ { :status_count => cookies[:status_count] }
380
410
  end
381
411
 
382
- delete '/counter' do
383
- { :result => cookies.delete(:counter) }
412
+ delete 'status_count' do
413
+ { :status_count => cookies.delete(:status_count) }
384
414
  end
415
+
385
416
  end
386
417
  ```
387
418
 
388
- To set more than value use hash-based syntax.
419
+ Use a hash-based syntax to set more than one value.
389
420
 
390
421
  ``` ruby
391
- cookies[:counter] = {
422
+ cookies[:status_count] = {
392
423
  :value => 0,
393
424
  :expires => Time.tomorrow,
394
- :domain => '.example.com',
425
+ :domain => '.twitter.com',
395
426
  :path => '/'
396
427
  }
397
- cookies[:counter][:value] +=1
428
+
429
+ cookies[:status_count][:value] +=1
398
430
  ```
399
431
 
400
432
  ## Redirecting
@@ -402,53 +434,54 @@ cookies[:counter][:value] +=1
402
434
  You can redirect to a new url temporarily (302) or permanently (301).
403
435
 
404
436
  ``` ruby
405
- redirect "/new_url"
437
+ redirect "/statuses"
406
438
  ```
407
439
 
408
440
  ``` ruby
409
- redirect "/new_url", :permanent => true
441
+ redirect "/statuses", :permanent => true
410
442
  ```
411
443
 
412
444
  ## Allowed Methods
413
445
 
414
446
  When you add a route for a resource, a route for the HTTP OPTIONS
415
447
  method will also be added. The response to an OPTIONS request will
416
- include an Allow header listing the supported methods.
448
+ include an "Allow" header listing the supported methods.
417
449
 
418
450
  ``` ruby
419
451
  class API < Grape::API
420
- get '/counter' do
421
- { :counter => Counter.count }
452
+
453
+ get '/rt_count' do
454
+ { :rt_count => current_user.rt_count }
422
455
  end
423
456
 
424
457
  params do
425
- requires :value, :type => Integer, :desc => 'value to add to counter'
458
+ requires :value, :type => Integer, :desc => 'Value to add to the rt count.'
426
459
  end
427
- put '/counter' do
428
- { :counter => Counter.incr(params.value) }
460
+ put '/rt_count' do
461
+ current_user.rt_count += params[:value].to_i
462
+ { :rt_count => current_user.rt_count }
429
463
  end
430
464
  end
431
465
  ```
432
466
 
433
467
  ``` shell
434
- curl -v -X OPTIONS http://localhost:3000/counter
468
+ curl -v -X OPTIONS http://localhost:3000/rt_count
435
469
 
436
- > OPTIONS /counter HTTP/1.1
437
- >
470
+ > OPTIONS /rt_count HTTP/1.1
471
+ >
438
472
  < HTTP/1.1 204 No Content
439
473
  < Allow: OPTIONS, GET, PUT
440
474
  ```
441
475
 
442
-
443
476
  If a request for a resource is made with an unsupported HTTP method, an
444
477
  HTTP 405 (Method Not Allowed) response will be returned.
445
478
 
446
479
  ``` shell
447
- curl -X DELETE -v http://localhost:3000/counter/
480
+ curl -X DELETE -v http://localhost:3000/rt_count/
448
481
 
449
- > DELETE /counter/ HTTP/1.1
482
+ > DELETE /rt_count/ HTTP/1.1
450
483
  > Host: localhost:3000
451
- >
484
+ >
452
485
  < HTTP/1.1 405 Method Not Allowed
453
486
  < Allow: OPTIONS, GET, PUT
454
487
  ```
@@ -470,8 +503,7 @@ error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
470
503
 
471
504
  ## Exception Handling
472
505
 
473
- Grape can be told to rescue all exceptions and instead return them in
474
- txt or json formats.
506
+ Grape can be told to rescue all exceptions and return them in txt or json formats.
475
507
 
476
508
  ``` ruby
477
509
  class Twitter::API < Grape::API
@@ -487,12 +519,29 @@ class Twitter::API < Grape::API
487
519
  end
488
520
  ```
489
521
 
490
- The error format can be specified using `error_format`. Available formats are
491
- `:json` and `:txt` (default).
522
+ The error format will match the request format. See "Content-Types" below.
523
+
524
+ Custom error formatters for existing and additional types can be defined with a proc.
492
525
 
493
526
  ``` ruby
494
527
  class Twitter::API < Grape::API
495
- error_format :json
528
+ error_formatter :txt, lambda { |message, backtrace, options, env|
529
+ "error: #{message} from #{backtrace}"
530
+ }
531
+ end
532
+ ```
533
+
534
+ You can also use a module or class.
535
+
536
+ ``` ruby
537
+ module CustomFormatter
538
+ def self.call(message, backtrace, options, env)
539
+ { message: message, backtrace: backtrace }
540
+ end
541
+ end
542
+
543
+ class Twitter::API < Grape::API
544
+ error_formatter :custom, CustomFormatter
496
545
  end
497
546
  ```
498
547
 
@@ -546,9 +595,9 @@ class API < Grape::API
546
595
  API.logger
547
596
  end
548
597
  end
549
- get '/hello' do
550
- logger.info "someone said hello"
551
- "hey there"
598
+ post '/statuses' do
599
+ ...
600
+ logger.info "#{current_user} has statused"
552
601
  end
553
602
  end
554
603
  ```
@@ -569,9 +618,8 @@ class API < Grape::API
569
618
  API.logger
570
619
  end
571
620
  end
572
- get '/hello' do
573
- logger.warning "someone said hello"
574
- "hey there"
621
+ get '/statuses' do
622
+ logger.warning "#{current_user} has statused"
575
623
  end
576
624
  end
577
625
  ```
@@ -581,23 +629,46 @@ end
581
629
  By default, Grape supports _XML_, _JSON_, _Atom_, _RSS_, and _text_ content-types.
582
630
  Serialization takes place automatically.
583
631
 
584
- Your API can declare additional types to support. Response format is determined by the
585
- request's extension, an explicit `format` parameter in the query string, or `Accept` header.
632
+ Your API can declare which types to support by using `content_type`. Response format
633
+ is determined by the request's extension, an explicit `format` parameter in the query
634
+ string, or `Accept` header.
635
+
636
+ The following API will only respond to the JSON content-type. All other requests will
637
+ fail with an HTTP 406 error code.
638
+
639
+ ``` ruby
640
+ class Twitter::API < Grape::API
641
+ format :json
642
+ content_type :json, "application/json"
643
+ end
644
+ ```
645
+
646
+ Custom formatters for existing and additional types can be defined with a proc.
586
647
 
587
648
  ``` ruby
588
649
  class Twitter::API < Grape::API
589
650
  content_type :xls, "application/vnd.ms-excel"
651
+ formatter :xls, lambda { |object, env| object.to_xls }
590
652
  end
591
653
  ```
592
654
 
593
- You can also set the default format. The order for choosing the format is the following.
655
+ You can also use a module or class.
594
656
 
595
- * Use the file extension, if specified. If the file is .json, choose the JSON format.
596
- * Use the value of the `format` parameter in the query string, if specified.
597
- * Use the format set by the `format` option, if specified.
598
- * Attempt to find an acceptable format from the `Accept` header.
599
- * Use the default format, if specified by the `default_format` option.
600
- * Default to `:txt` otherwise.
657
+ ``` ruby
658
+ module XlsFormatter
659
+ def self.call(object, env)
660
+ object.to_xls
661
+ end
662
+ end
663
+
664
+ class Twitter::API < Grape::API
665
+ content_type :xls, "application/vnd.ms-excel"
666
+ formatter :xls, XlsFormatter
667
+ end
668
+ ```
669
+
670
+ The default format is `:txt`. You can set the preferred format for an API that
671
+ supports multiple formats with `format`.
601
672
 
602
673
  ``` ruby
603
674
  class Twitter::API < Grape::API
@@ -605,19 +676,39 @@ class Twitter::API < Grape::API
605
676
  end
606
677
  ```
607
678
 
679
+ You can set the fallback, default format with `default_format`.
680
+
608
681
  ``` ruby
609
682
  class Twitter::API < Grape::API
610
683
  default_format :json
611
684
  end
612
685
  ```
613
686
 
614
- You can override the content-type by setting the `Content-Type` header.
687
+ Available formats are the following.
688
+
689
+ * `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump`
690
+ * `:xml`: use object's `to_xml` when available, usually via `MultiXml`, otherwise call `to_s`
691
+ * `:txt`: use object's `to_txt` when available, otherwise `to_s`
692
+ * `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
693
+
694
+ The order for choosing the format is the following.
695
+
696
+ * Use the file extension, if specified. If the file is .json, choose the JSON format.
697
+ * Use the value of the `format` parameter in the query string, if specified.
698
+ * Use the format set by the `format` option, if specified.
699
+ * Attempt to find an acceptable format from the `Accept` header.
700
+ * Use the default format, if specified by the `default_format` option.
701
+ * Default to `:txt` otherwise.
702
+
703
+ ## Content-type
704
+
705
+ You can override the content-type of the response by setting the `Content-Type` header.
615
706
 
616
707
  ``` ruby
617
708
  class API < Grape::API
618
- get '/script' do
709
+ get '/home_timeline_js' do
619
710
  content_type "application/javascript"
620
- "var x = 1;"
711
+ "var statuses = ...;"
621
712
  end
622
713
  end
623
714
  ```
@@ -656,21 +747,21 @@ array.
656
747
  ``` ruby
657
748
  module API
658
749
  module Entities
659
- class User < Grape::Entity
660
- expose :first_name, :last_name
661
- expose :field, :documentation => { :type => "string", :desc => "words go here" }
662
- expose :email, :if => { :type => :full }
663
- expose :user_type, user_id, :if => lambda{ |user,options| user.confirmed? }
664
- expose(:name) { |user,options| [ user.first_name, user.last_name ].join(' ')}
665
- expose :latest_status, :using => API::Status, :as => :status
750
+ class Status < Grape::Entity
751
+ expose :user_name
752
+ expose :text, :documentation => { :type => "string", :desc => "Status update text." }
753
+ expose :ip, :if => { :type => :full }
754
+ expose :user_type, user_id, :if => lambda{ |status, options| status.user.public? }
755
+ expose :digest { |status, options| Digest::MD5.hexdigest(satus.txt) }
756
+ expose :replies, :using => API::Status, :as => :replies
666
757
  end
667
758
  end
668
759
  end
669
760
 
670
761
  module API
671
762
  module Entities
672
- class UserDetailed < API::Entities::User
673
- expose :account_id
763
+ class StatusDetailed < API::Entities::Status
764
+ expose :internal_id
674
765
  end
675
766
  end
676
767
  end
@@ -682,20 +773,18 @@ Grape ships with a DSL to easily define entities within the context
682
773
  of an existing class:
683
774
 
684
775
  ```ruby
685
- class User
776
+ class Status
686
777
  include Grape::Entity::DSL
687
778
 
688
- entity :name, :email do
689
- expose :advanced, if: :conditional
779
+ entity :text, :user_id do
780
+ expose :detailed, if: :conditional
690
781
  end
691
782
  end
692
783
  ```
693
784
 
694
- The above will automatically create a `User::Entity` class and
695
- define properties on it according to the same rules as above. If
696
- you only want to define simple exposures you don't have to supply
697
- a block and can instead simply supply a list of comma-separated
698
- symbols.
785
+ The above will automatically create a `Status::Entity` class and define properties on it according
786
+ to the same rules as above. If you only want to define simple exposures you don't have to supply
787
+ a block and can instead simply supply a list of comma-separated symbols.
699
788
 
700
789
  ### Using Entities
701
790
 
@@ -707,16 +796,16 @@ If the entity includes documentation it can be included in an endpoint's descrip
707
796
 
708
797
  ``` ruby
709
798
  module API
710
- class Users < Grape::API
799
+ class Statuses < Grape::API
711
800
  version 'v1'
712
801
 
713
- desc 'User index', {
714
- :object_fields => API::Entities::User.documentation
802
+ desc 'Statuses index', {
803
+ :object_fields => API::Entities::Status.documentation
715
804
  }
716
- get '/users' do
717
- @users = User.all
805
+ get '/statues' do
806
+ statuses = Status.all
718
807
  type = current_user.admin? ? :full : :default
719
- present @users, with: API::Entities::User, :type => type
808
+ present statues, with: API::Entities::Status, :type => type
720
809
  end
721
810
  end
722
811
  end
@@ -724,28 +813,25 @@ end
724
813
 
725
814
  ### Entity Organization
726
815
 
727
- In addition to separately organizing entities, it may be useful to
728
- put them as namespaced classes underneath the model they represent.
729
- For example:
816
+ In addition to separately organizing entities, it may be useful to put them as namespaced
817
+ classes underneath the model they represent.
730
818
 
731
819
  ```ruby
732
- class User
820
+ class Status
733
821
  def entity
734
- Entity.new(self)
822
+ Status.new(self)
735
823
  end
736
824
 
737
825
  class Entity < Grape::Entity
738
- expose :name, :email
826
+ expose :text, :user_id
739
827
  end
740
828
  end
741
829
  ```
742
830
 
743
- If you organize your entities this way, Grape will automatically
744
- detect the `Entity` class and use it to present your models. In
745
- this example, if you added `present User.new` to your endpoint,
746
- Grape would automatically detect that there is a `User::Entity`
747
- class and use that as the representative entity. This can still
748
- be overridden by using the `:with` option or an explicit
831
+ If you organize your entities this way, Grape will automatically detect the `Entity` class and
832
+ use it to present your models. In this example, if you added `present User.new` to your endpoint,
833
+ Grape would automatically detect that there is a `Status::Entity` class and use that as the
834
+ representative entity. This can still be overridden by using the `:with` option or an explicit
749
835
  `represents` call.
750
836
 
751
837
  ### Caveats
@@ -757,7 +843,7 @@ However, when `object.check` equals "bar" both `field_b` and `foo` will be expos
757
843
  ```ruby
758
844
  module API
759
845
  module Entities
760
- class User < Grape::Entity
846
+ class Status < Grape::Entity
761
847
  expose :field_a, :foo, :if => lambda { |object, options| object.check == "foo" }
762
848
  expose :field_b, :foo, :if => lambda { |object, options| object.check == "bar" }
763
849
  end
@@ -770,7 +856,7 @@ This can be problematic, when you have mixed collections. Using `respond_to?` is
770
856
  ```ruby
771
857
  module API
772
858
  module Entities
773
- class User < Grape::Entity
859
+ class Status < Grape::Entity
774
860
  expose :field_a, :if => lambda { |object, options| object.check == "foo" }
775
861
  expose :field_b, :if => lambda { |object, options| object.check == "bar" }
776
862
  expose :foo, :if => lambda { |object, options| object.respond_to?(:foo) }
@@ -779,9 +865,21 @@ module API
779
865
  end
780
866
  ```
781
867
 
868
+ ## Hypermedia and other RESTful Representations
869
+
870
+ Although Grape ships with its own entity support, it's also possible to use it with other frameworks and renderers.
871
+
872
+ ### Hypermedia
873
+
874
+ Use [Roar](https://github.com/apotonick/roar). Include `Roar::Representer::JSON` in your models or call `to_json` explicitly on representers in your API.
875
+
876
+ ### Rabl
877
+
878
+ [Rabl](https://github.com/nesquena/rabl) is supported via the [grape-rabl](https://github.com/LTe/grape-rabl) gem.
879
+
782
880
  ## Describing and Inspecting an API
783
881
 
784
- Grape routes can be reflected at runtime. This can notably be useful for generating
882
+ Grape routes can be reflected at runtime. This can notably be useful for generating
785
883
  documentation.
786
884
 
787
885
  Grape exposes arrays of API versions and compiled routes. Each route
@@ -797,7 +895,7 @@ TwitterAPI::routes[0].route_version # yields 'v1'
797
895
  TwitterAPI::routes[0].route_description # etc.
798
896
  ```
799
897
 
800
- It's possible to retrieve the information about the current route from within an API
898
+ It's possible to retrieve the information about the current route from within an API
801
899
  call with `route`.
802
900
 
803
901
  ``` ruby
@@ -816,29 +914,29 @@ end
816
914
 
817
915
  Grape by default anchors all request paths, which means that the request URL
818
916
  should match from start to end to match, otherwise a `404 Not Found` is
819
- returned. However, this is sometimes not what you want, because it is not always
917
+ returned. However, this is sometimes not what you want, because it is not always
820
918
  known upfront what can be expected from the call. This is because Rack-mount by
821
- default anchors requests to match from the start to the end, or not at all.
919
+ default anchors requests to match from the start to the end, or not at all.
822
920
  Rails solves this problem by using a `:anchor => false` option in your routes.
823
921
  In Grape this option can be used as well when a method is defined.
824
922
 
825
923
  For instance when you're API needs to get part of an URL, for instance:
826
924
 
827
925
  ``` ruby
828
- class UrlAPI < Grape::API
829
- namespace :urls do
830
- get '/(*:url)', :anchor => false do
831
- some_data
926
+ class TwitterAPI < Grape::API
927
+ namespace :statues do
928
+ get '/(*:status)', :anchor => false do
929
+
832
930
  end
833
931
  end
834
932
  end
835
933
  ```
836
934
 
837
- This will match all paths starting with '/urls/'. There is one caveat though:
838
- the `params[:url]` parameter only holds the first part of the request url.
935
+ This will match all paths starting with '/statuses/'. There is one caveat though:
936
+ the `params[:status]` parameter only holds the first part of the request url.
839
937
  Luckily this can be circumvented by using the described above syntax for path
840
938
  specification and using the `PATH_INFO` Rack environment variable, using
841
- `env["PATH_INFO"]`. This will hold everything that comes after the '/urls/'
939
+ `env["PATH_INFO"]`. This will hold everything that comes after the '/statuses/'
842
940
  part.
843
941
 
844
942
  ## Writing Tests
@@ -895,7 +993,7 @@ describe Twitter::API do
895
993
  it "returns a status by id" do
896
994
  status = Status.create!
897
995
  get "/api/v1/statuses/#{status.id}"
898
- resonse.body.should == status.to_json
996
+ response.body.should == status.to_json
899
997
  end
900
998
  end
901
999
  end