grape 0.2.1.1 → 0.2.2

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.

data/.gitignore CHANGED
@@ -16,6 +16,7 @@ tmtags
16
16
 
17
17
  ## VIM
18
18
  *.swp
19
+ *.swo
19
20
 
20
21
  ## RUBYMINE
21
22
  .idea
@@ -1,6 +1,27 @@
1
- 0.2.1.1 (1/11/2013)
1
+ 0.2.2 (Next Release)
2
2
  ====================
3
- * Fix: CVE-2013-0175, `multi_xml` parse vulnerability, require 'multi_xml' 0.5.2 - [@dblock](http://github.com/dblock).
3
+
4
+ Features
5
+ --------
6
+
7
+ * [#201](https://github.com/intridea/grape/pull/201), [#236](https://github.com/intridea/grape/pull/236), [#221](https://github.com/intridea/grape/pull/221): Added coercion and validations support to `params` DSL - [@schmurfy](https://github.com/schmurfy), [@tim-vandecasteele](https://github.com/tim-vandecasteele), [@adamgotterer](https://github.com/adamgotterer).
8
+ * [#204](https://github.com/intridea/grape/pull/204): Added ability to declare shared `params` at `namespace` level - [@tim-vandecasteele](https://github.com/tim-vandecasteele).
9
+ * [#234](https://github.com/intridea/grape/pull/234): Added a DSL for creating entities via mixin - [@mbleigh](https://github.com/mbleigh).
10
+ * [#240](https://github.com/intridea/grape/pull/240): Define API response format from a query string `format` parameter, if specified - [@neetiraj](https://github.com/neetiraj).
11
+ * Adds Endpoint#declared to easily filter out unexpected params. - [@mbleigh](https://github.com/mbleigh)
12
+
13
+ Fixes
14
+ -----
15
+
16
+ * [#248](https://github.com/intridea/grape/pull/248): Fix: API `version` returns last version set - [@narkoz](https://github.com/narkoz).
17
+ * [#242](https://github.com/intridea/grape/issues/242): Fix: permanent redirect status should be `301`, was `304` - [@adamgotterer](https://github.com/adamgotterer).
18
+ * [#211](https://github.com/intridea/grape/pull/211): Fix: custom validations are no longer triggered when optional and parameter is not present - [@adamgotterer](https://github.com/adamgotterer).
19
+ * [#210](https://github.com/intridea/grape/pull/210): Fix: `Endpoint#body_params` causing undefined method 'size' - [@adamgotterer](https://github.com/adamgotterer).
20
+ * [#205](https://github.com/intridea/grape/pull/205): Fix: Corrected parsing of empty JSON body on POST/PUT - [@tim-vandecasteele](https://github.com/tim-vandecasteele).
21
+ * [#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
+ * [#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
+ * [#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)
4
25
 
5
26
  0.2.1 (7/11/2012)
6
27
  =================
data/Gemfile CHANGED
@@ -10,4 +10,6 @@ group :development, :test do
10
10
  gem 'rb-fsevent'
11
11
  gem 'growl'
12
12
  gem 'json'
13
+ gem 'rspec'
14
+ gem 'rack-test', "~> 0.6.2", :require => "rack/test"
13
15
  end
@@ -1,12 +1,14 @@
1
- # Grape [![Build Status](http://travis-ci.org/intridea/grape.png?branch=frontier)](http://travis-ci.org/intridea/grape)
1
+ ![grape logo](https://github.com/intridea/grape/wiki/grape_logo.png)
2
2
 
3
3
  ## What is Grape?
4
4
 
5
- Grape is a REST-like API micro-framework for Ruby. It is built to complement
6
- existing web application frameworks such as Rails and Sinatra by providing a
7
- simple DSL to easily provide APIs. It has built-in support for common
8
- conventions such as multiple formats, subdomain/prefix restriction, and
9
- versioning.
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,
9
+ content negotiation, versioning and much more.
10
+
11
+ [![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape)
10
12
 
11
13
  ## Project Tracking
12
14
 
@@ -23,6 +25,8 @@ If you're using Bundler, add the gem to Gemfile.
23
25
 
24
26
  gem 'grape'
25
27
 
28
+ Run `bundle install`.
29
+
26
30
  ## Basic Usage
27
31
 
28
32
  Grape APIs are Rack applications that are created by subclassing `Grape::API`.
@@ -32,6 +36,7 @@ the context of recreating parts of the Twitter API.
32
36
  ``` ruby
33
37
  class Twitter::API < Grape::API
34
38
  version 'v1', :using => :header, :vendor => 'twitter'
39
+ format :json
35
40
 
36
41
  helpers do
37
42
  def current_user
@@ -44,19 +49,30 @@ class Twitter::API < Grape::API
44
49
  end
45
50
 
46
51
  resource :statuses do
52
+
53
+ desc "Returns a public timeline."
47
54
  get :public_timeline do
48
55
  Tweet.limit(20)
49
56
  end
50
57
 
58
+ desc "Returns a personal timeline."
51
59
  get :home_timeline do
52
60
  authenticate!
53
61
  current_user.home_timeline
54
62
  end
55
63
 
64
+ desc "Returns a tweet."
65
+ params do
66
+ requires :id, :type => Integer, :desc => "Tweet id."
67
+ end
56
68
  get '/show/:id' do
57
69
  Tweet.find(params[:id])
58
70
  end
59
-
71
+
72
+ desc "Creates a tweet."
73
+ params do
74
+ requires :status, :type => String, :desc => "Your status."
75
+ end
60
76
  post :update do
61
77
  authenticate!
62
78
  Tweet.create(
@@ -66,28 +82,13 @@ class Twitter::API < Grape::API
66
82
  end
67
83
  end
68
84
 
69
- resource :account do
70
- before { authenticate! }
71
-
72
- get '/private' do
73
- "Congratulations, you found the secret!"
74
- end
75
- end
76
-
77
- end
78
- ```
79
-
80
- Optionally, you can define requirements for your named route parameters using regular expressions. The route will match only if
81
- all requirements are met.
82
-
83
- ```ruby
84
- get '/show/:id', :requirements => { :id => /[0-9]*/ } do
85
- Tweet.find(params[:id])
86
85
  end
87
86
  ```
88
87
 
89
88
  ## Mounting
90
89
 
90
+ ### Rack
91
+
91
92
  The above sample creates a Rack application that can be run from a rackup *config.ru* file
92
93
  with `rackup`:
93
94
 
@@ -102,13 +103,21 @@ And would respond to the following routes:
102
103
  GET /statuses/show/:id(.json)
103
104
  POST /statuses/update(.json)
104
105
 
106
+ ### Rails
107
+
105
108
  In a Rails application, modify *config/routes*:
106
109
 
107
110
  ``` ruby
108
111
  mount Twitter::API => "/"
109
112
  ```
110
113
 
111
- You can mount multiple API implementations inside another one.
114
+ Note that you will need to restart Rails to pick up changes in your API classes
115
+ (see [Issue 131](https://github.com/intridea/grape/issues/131)).
116
+
117
+ ### Modules
118
+
119
+ You can mount multiple API implementations inside another one. These don't have to be
120
+ different versions, but may be components of the same API.
112
121
 
113
122
  ```ruby
114
123
  class Twitter::API < Grape::API
@@ -119,23 +128,23 @@ end
119
128
 
120
129
  ## Versioning
121
130
 
122
- There are three strategies in which clients can reach your API's endpoints: `:header`, `:path` and `:param`. The default strategy is `:header`.
123
-
131
+ There are three strategies in which clients can reach your API's endpoints: `:header`,
132
+ `:path` and `:param`. The default strategy is `:path`.
124
133
 
125
134
  ### Header
126
135
 
127
136
  ```ruby
128
- version 'v1', :using => :header
137
+ version 'v1', :using => :header, :vendor => 'twitter'
129
138
  ```
130
139
 
131
- Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
140
+ Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.
132
141
 
133
142
  curl -H Accept=application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
134
143
 
135
- By default, the first matching version is used when no Accept header is
144
+ By default, the first matching version is used when no `Accept` header is
136
145
  supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
137
- one could use the `:strict` option. When this option is set to `true`, a `404 Not found` error
138
- is returned when no correct Accept header is supplied.
146
+ one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
147
+ is returned when no correct `Accept` header is supplied.
139
148
 
140
149
  ### Path
141
150
 
@@ -147,19 +156,18 @@ Using this versioning strategy, clients should pass the desired version in the U
147
156
 
148
157
  curl -H http://localhost:9292/v1/statuses/public_timeline
149
158
 
150
- Serialization takes place automatically.
151
-
152
159
  ### Param
153
160
 
154
161
  ```ruby
155
162
  version 'v1', :using => :param
156
163
  ```
157
164
 
158
- Using this versioning strategy, clients should pass the desired version as a request parameter, either in the URL query string or in the request body.
165
+ Using this versioning strategy, clients should pass the desired version as a request parameter,
166
+ either in the URL query string or in the request body.
159
167
 
160
168
  curl -H http://localhost:9292/events?apiver=v1
161
169
 
162
- The default name for the query parameter is 'apiver' but can be specified using the :parameter option.
170
+ The default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.
163
171
 
164
172
  ```ruby
165
173
  version 'v1', :using => :param, :parameter => "v"
@@ -167,14 +175,25 @@ version 'v1', :using => :param, :parameter => "v"
167
175
 
168
176
  curl -H http://localhost:9292/events?v=v1
169
177
 
178
+ ## Describing Methods
179
+
180
+ You can add a description to API methods and namespaces.
181
+
182
+ ``` ruby
183
+ desc "Returns a reticulated spline."
184
+ get "spline/:id" do
185
+ Spline.find(params[:id])
186
+ end
187
+ ```
188
+
170
189
  ## Parameters
171
190
 
172
- Parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
191
+ Request parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
173
192
  along with any named parameters you specify in your route strings.
174
193
 
175
194
  ```ruby
176
195
  get do
177
- Article.order(params[:sort_by])
196
+ Article.order(params[:sort_by])
178
197
  end
179
198
  ```
180
199
 
@@ -192,9 +211,113 @@ post '/json_endpoint' do
192
211
  end
193
212
  ```
194
213
 
214
+ ## Parameter Validation and Coercion
215
+
216
+ You can define validations and coercion options for your parameters using a `params` block.
217
+
218
+ ```ruby
219
+ params do
220
+ 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
226
+ end
227
+ end
228
+ get ':id' do
229
+ # params[:id] is an Integer
230
+ end
231
+ ```
232
+
233
+ When a type is specified an implicit validation is done after the coercion to ensure
234
+ the output type is the one declared.
235
+
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]`.
238
+
239
+ ### Namespace Validation and Coercion
240
+ Namespaces allow parameter definitions and apply to every method within the namespace.
241
+
242
+ ```ruby
243
+ namespace :shelves do
244
+ params do
245
+ requires :shelf_id, type: Integer, desc: "A shelf."
246
+ end
247
+ namespace ":shelf_id" do
248
+ desc "Retrieve a book from a shelf."
249
+ params do
250
+ requires :book_id, type: Integer, desc: "A book."
251
+ end
252
+ get ":book_id" do
253
+ # params[:shelf_id] defines a shelf
254
+ # params[:book_id] defines a book
255
+ end
256
+ end
257
+ end
258
+ ```
259
+
260
+ ### Custom Validators
261
+ ```ruby
262
+ class AlphaNumeric < Grape::Validations::Validator
263
+ def validate_param!(attr_name, params)
264
+ unless params[attr_name] =~ /^[[:alnum:]]+$/
265
+ throw :error, :status => 400, :message => "#{attr_name}: must consist of alpha-numeric characters"
266
+ end
267
+ end
268
+ end
269
+ ```
270
+
271
+ ```ruby
272
+ params do
273
+ requires :username, :alpha_numeric => true
274
+ end
275
+ ```
276
+
277
+ You can also create custom classes that take parameters.
278
+
279
+ ```ruby
280
+ class Length < Grape::Validations::SingleOptionValidator
281
+ 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
285
+ end
286
+ end
287
+ ```
288
+
289
+ ```ruby
290
+ params do
291
+ requires :name, :length => 5
292
+ end
293
+ ```
294
+
295
+ ### Validation Errors
296
+
297
+ When validation and coercion erros occur an exception of type `ValidationError` is raised.
298
+ 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.
300
+
301
+ ```ruby
302
+ rescue_from ValidationError do |e|
303
+ Rack::Response.new({
304
+ 'status' => e.status,
305
+ 'message' => e.message,
306
+ 'param' => e.param
307
+ }.to_json, e.status)
308
+ end
309
+ ```
310
+
195
311
  ## Headers
196
312
 
197
- Headers are available through the `env` hash object.
313
+ Headers are available through the `header` helper or the `env` hash object.
314
+
315
+ ```ruby
316
+ get do
317
+ content_type = header['Content-type']
318
+ ...
319
+ end
320
+ ```
198
321
 
199
322
  ```ruby
200
323
  get do
@@ -203,10 +326,21 @@ get do
203
326
  end
204
327
  ```
205
328
 
329
+ ## Routes
330
+
331
+ Optionally, you can define requirements for your named route parameters using regular
332
+ expressions. The route will match only if all requirements are met.
333
+
334
+ ```ruby
335
+ get '/show/:id', :requirements => { :id => /[0-9]*/ } do
336
+ Tweet.find(params[:id])
337
+ end
338
+ ```
339
+
206
340
  ## Helpers
207
341
 
208
342
  You can define helper methods that your endpoints can use with the `helpers`
209
- macro by either giving a block or a module:
343
+ macro by either giving a block or a module.
210
344
 
211
345
  ``` ruby
212
346
  module MyHelpers
@@ -235,7 +369,7 @@ end
235
369
 
236
370
  ## Cookies
237
371
 
238
- You can set, get and delete your cookies very simply using `cookies` method:
372
+ You can set, get and delete your cookies very simply using `cookies` method.
239
373
 
240
374
  ``` ruby
241
375
  class API < Grape::API
@@ -251,7 +385,7 @@ class API < Grape::API
251
385
  end
252
386
  ```
253
387
 
254
- To set more than value use hash-based syntax:
388
+ To set more than value use hash-based syntax.
255
389
 
256
390
  ``` ruby
257
391
  cookies[:counter] = {
@@ -262,30 +396,73 @@ cookies[:counter] = {
262
396
  }
263
397
  cookies[:counter][:value] +=1
264
398
  ```
265
- ## Redirect
266
399
 
267
- You can redirect to a new url
400
+ ## Redirecting
401
+
402
+ You can redirect to a new url temporarily (302) or permanently (301).
268
403
 
269
404
  ``` ruby
270
405
  redirect "/new_url"
271
406
  ```
272
407
 
273
- use permanent redirect
274
-
275
408
  ``` ruby
276
409
  redirect "/new_url", :permanent => true
277
410
  ```
278
411
 
279
- ## Raising Errors
412
+ ## Allowed Methods
280
413
 
281
- You can raise errors explicitly.
414
+ When you add a route for a resource, a route for the HTTP OPTIONS
415
+ method will also be added. The response to an OPTIONS request will
416
+ include an Allow header listing the supported methods.
417
+
418
+ ``` ruby
419
+ class API < Grape::API
420
+ get '/counter' do
421
+ { :counter => Counter.count }
422
+ end
423
+
424
+ params do
425
+ requires :value, :type => Integer, :desc => 'value to add to counter'
426
+ end
427
+ put '/counter' do
428
+ { :counter => Counter.incr(params.value) }
429
+ end
430
+ end
431
+ ```
432
+
433
+ ``` shell
434
+ curl -v -X OPTIONS http://localhost:3000/counter
435
+
436
+ > OPTIONS /counter HTTP/1.1
437
+ >
438
+ < HTTP/1.1 204 No Content
439
+ < Allow: OPTIONS, GET, PUT
440
+ ```
441
+
442
+
443
+ If a request for a resource is made with an unsupported HTTP method, an
444
+ HTTP 405 (Method Not Allowed) response will be returned.
445
+
446
+ ``` shell
447
+ curl -X DELETE -v http://localhost:3000/counter/
448
+
449
+ > DELETE /counter/ HTTP/1.1
450
+ > Host: localhost:3000
451
+ >
452
+ < HTTP/1.1 405 Method Not Allowed
453
+ < Allow: OPTIONS, GET, PUT
454
+ ```
455
+
456
+ ## Raising Exceptions
457
+
458
+ You can abort the execution of an API method by raising errors with `error!`.
282
459
 
283
460
  ``` ruby
284
461
  error!("Access Denied", 401)
285
462
  ```
286
463
 
287
- You can also return JSON formatted objects explicitly by raising error! and
288
- passing a hash instead of a message.
464
+ You can also return JSON formatted objects by raising error! and passing a hash
465
+ instead of a message.
289
466
 
290
467
  ``` ruby
291
468
  error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
@@ -294,7 +471,7 @@ error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
294
471
  ## Exception Handling
295
472
 
296
473
  Grape can be told to rescue all exceptions and instead return them in
297
- text or json formats.
474
+ txt or json formats.
298
475
 
299
476
  ``` ruby
300
477
  class Twitter::API < Grape::API
@@ -360,7 +537,7 @@ end
360
537
  class from Ruby's standard library.
361
538
 
362
539
  To log messages from within an endpoint, you need to define a helper to make the logger
363
- available in the endpoint context:
540
+ available in the endpoint context.
364
541
 
365
542
  ``` ruby
366
543
  class API < Grape::API
@@ -376,7 +553,7 @@ class API < Grape::API
376
553
  end
377
554
  ```
378
555
 
379
- You can also set your own logger:
556
+ You can also set your own logger.
380
557
 
381
558
  ``` ruby
382
559
  class MyLogger
@@ -402,8 +579,10 @@ end
402
579
  ## Content-Types
403
580
 
404
581
  By default, Grape supports _XML_, _JSON_, _Atom_, _RSS_, and _text_ content-types.
582
+ Serialization takes place automatically.
583
+
405
584
  Your API can declare additional types to support. Response format is determined by the
406
- request's extension or `Accept` header.
585
+ request's extension, an explicit `format` parameter in the query string, or `Accept` header.
407
586
 
408
587
  ``` ruby
409
588
  class Twitter::API < Grape::API
@@ -414,7 +593,8 @@ end
414
593
  You can also set the default format. The order for choosing the format is the following.
415
594
 
416
595
  * Use the file extension, if specified. If the file is .json, choose the JSON format.
417
- * Use the format, if specified by the `format` option.
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.
418
598
  * Attempt to find an acceptable format from the `Accept` header.
419
599
  * Use the default format, if specified by the `default_format` option.
420
600
  * Default to `:txt` otherwise.
@@ -422,6 +602,11 @@ You can also set the default format. The order for choosing the format is the fo
422
602
  ``` ruby
423
603
  class Twitter::API < Grape::API
424
604
  format :json
605
+ end
606
+ ```
607
+
608
+ ``` ruby
609
+ class Twitter::API < Grape::API
425
610
  default_format :json
426
611
  end
427
612
  ```
@@ -437,77 +622,6 @@ class API < Grape::API
437
622
  end
438
623
  ```
439
624
 
440
- ## Writing Tests
441
-
442
- You can test a Grape API with RSpec by making HTTP requests and examining the response.
443
-
444
- ### Writing Tests with Rack
445
-
446
- Use `rack-test` and define your API as `app`.
447
-
448
- ```ruby
449
- require 'spec_helper'
450
-
451
- describe Twitter::API do
452
- include Rack::Test::Methods
453
-
454
- def app
455
- Twitter::API
456
- end
457
-
458
- describe Twitter::API do
459
- describe "GET /api/v1/statuses" do
460
- it "returns an empty array of statuses" do
461
- get "/api/v1/statuses"
462
- last_response.status.should == 200
463
- JSON.parse(response.body).should == []
464
- end
465
- end
466
- describe "GET /api/v1/statuses/:id" do
467
- it "returns a status by id" do
468
- status = Status.create!
469
- get "/api/v1/statuses/#{status.id}"
470
- last_response.body.should == status.to_json
471
- end
472
- end
473
- end
474
- end
475
- ```
476
-
477
- ### Writing Tests with Rails
478
-
479
- ``` ruby
480
- require 'spec_helper'
481
-
482
- describe Twitter::API do
483
- describe "GET /api/v1/statuses" do
484
- it "returns an empty array of statuses" do
485
- get "/api/v1/statuses"
486
- response.status.should == 200
487
- JSON.parse(response.body).should == []
488
- end
489
- end
490
- describe "GET /api/v1/statuses/:id" do
491
- it "returns a status by id" do
492
- status = Status.create!
493
- get "/api/v1/statuses/#{status.id}"
494
- resonse.body.should == status.to_json
495
- end
496
- end
497
- end
498
- ```
499
-
500
- In Rails, HTTP request tests would go into the `spec/request` group. You may want your API code to go into
501
- `app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
502
-
503
- ```ruby
504
- RSpec.configure do |config|
505
- config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
506
- :file_path => /spec\/api/
507
- }
508
- end
509
- ```
510
-
511
625
  ## Reusable Responses with Entities
512
626
 
513
627
  Entities are a reusable means for converting Ruby objects to API responses.
@@ -518,22 +632,22 @@ ever larger responses, using inheritance.
518
632
 
519
633
  Entities inherit from Grape::Entity, and define a simple DSL. Exposures can use
520
634
  runtime options to determine which fields should be visible, these options are
521
- available to :if, :unless, and :proc. The option keys :version and :collection
522
- will always be defined. The :version key is defined as api.version. The
523
- :collection key is boolean, and defined as true if the object presented is an
635
+ available to `:if`, `:unless`, and `:proc`. The option keys `:version` and `:collection`
636
+ will always be defined. The `:version` key is defined as `api.version`. The
637
+ `:collection` key is boolean, and defined as `true` if the object presented is an
524
638
  array.
525
639
 
526
640
  * `expose SYMBOLS`
527
641
  * define a list of fields which will always be exposed
528
642
  * `expose SYMBOLS, HASH`
529
- * HASH keys include :if, :unless, :proc, :as, :using, :format_with, :documentation
530
- * :if and :unless accept hashes (passed during runtime) or procs (arguments are object and options)
531
- * `expose SYMBOL, {:format_with => :formatter}`
643
+ * HASH keys include `:if`, `:unless`, `:proc`, `:as`, `:using`, `:format_with`, `:documentation`
644
+ * `:if` and `:unless` accept hashes (passed during runtime) or procs (arguments are object and options)
645
+ * `expose SYMBOL, { :format_with => :formatter }`
532
646
  * expose a value, formatting it first
533
- * :format_with can only be applied to one exposure at a time
534
- * `expose SYMBOL, {:as => "alias"}`
647
+ * `:format_with` can only be applied to one exposure at a time
648
+ * `expose SYMBOL, { :as => "alias" }`
535
649
  * Expose a value, changing its hash key from SYMBOL to alias
536
- * :as can only be applied to one exposure at a time
650
+ * `:as` can only be applied to one exposure at a time
537
651
  * `expose SYMBOL BLOCK`
538
652
  * block arguments are object and options
539
653
  * expose the value returned by the block
@@ -544,10 +658,10 @@ module API
544
658
  module Entities
545
659
  class User < Grape::Entity
546
660
  expose :first_name, :last_name
547
- expose :field, :documentation => {:type => "string", :desc => "words go here"}
548
- expose :email, :if => {:type => :full}
549
- expose :user_type, user_id, :if => lambda{|user,options| user.confirmed?}
550
- expose(:name){|user,options| [user.first_name, user.last_name].join(' ')}
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(' ')}
551
665
  expose :latest_status, :using => API::Status, :as => :status
552
666
  end
553
667
  end
@@ -562,11 +676,32 @@ module API
562
676
  end
563
677
  ```
564
678
 
679
+ #### Using the Exposure DSL
680
+
681
+ Grape ships with a DSL to easily define entities within the context
682
+ of an existing class:
683
+
684
+ ```ruby
685
+ class User
686
+ include Grape::Entity::DSL
687
+
688
+ entity :name, :email do
689
+ expose :advanced, if: :conditional
690
+ end
691
+ end
692
+ ```
693
+
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.
699
+
565
700
  ### Using Entities
566
701
 
567
- Once an entity is defined, it can be used within endpoints, by calling #present. The #present
702
+ Once an entity is defined, it can be used within endpoints, by calling `present`. The `present`
568
703
  method accepts two arguments, the object to be presented and the options associated with it. The
569
- options hash must always include :with, which defines the entity to expose.
704
+ options hash must always include `:with`, which defines the entity to expose.
570
705
 
571
706
  If the entity includes documentation it can be included in an endpoint's description.
572
707
 
@@ -587,32 +722,58 @@ module API
587
722
  end
588
723
  ```
589
724
 
725
+ ### Entity Organization
726
+
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:
730
+
731
+ ```ruby
732
+ class User
733
+ def entity
734
+ Entity.new(self)
735
+ end
736
+
737
+ class Entity < Grape::Entity
738
+ expose :name, :email
739
+ end
740
+ end
741
+ ```
742
+
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
749
+ `represents` call.
750
+
590
751
  ### Caveats
591
752
 
592
753
  Entities with duplicate exposure names and conditions will silently overwrite one another.
593
- In the following example, when object#check equals "foo", only afield will be exposed.
594
- However, when object#check equals "bar" both bfield and foo will be exposed.
754
+ In the following example, when `object.check` equals "foo", only `field_a` will be exposed.
755
+ However, when `object.check` equals "bar" both `field_b` and `foo` will be exposed.
595
756
 
596
757
  ```ruby
597
758
  module API
598
759
  module Entities
599
760
  class User < Grape::Entity
600
- expose :afield, :foo, :if => lambda{|object,options| object.check=="foo"}
601
- expose :bfield, :foo, :if => lambda{|object,options| object.check=="bar"}
761
+ expose :field_a, :foo, :if => lambda { |object, options| object.check == "foo" }
762
+ expose :field_b, :foo, :if => lambda { |object, options| object.check == "bar" }
602
763
  end
603
764
  end
604
765
  end
605
766
  ```
606
767
 
607
- This can be problematic, when you have mixed collections. Using #respond_to? is safer.
768
+ This can be problematic, when you have mixed collections. Using `respond_to?` is safer.
608
769
 
609
770
  ```ruby
610
771
  module API
611
772
  module Entities
612
773
  class User < Grape::Entity
613
- expose :afield, :if => lambda{|object,options| object.check=="foo"}
614
- expose :bfield, :if => lambda{|object,options| object.check=="bar"}
615
- expose :foo, :if => lambda{object,options| object.respond_to?(:foo)}
774
+ expose :field_a, :if => lambda { |object, options| object.check == "foo" }
775
+ expose :field_b, :if => lambda { |object, options| object.check == "bar" }
776
+ expose :foo, :if => lambda { |object, options| object.respond_to?(:foo) }
616
777
  end
617
778
  end
618
779
  end
@@ -620,31 +781,10 @@ end
620
781
 
621
782
  ## Describing and Inspecting an API
622
783
 
623
- Grape lets you add a description to an API along with any other optional
624
- elements that can also be inspected at runtime.
625
- This can be useful for generating documentation. If the response
626
- requires documentation, consider using an entity.
627
-
628
- ``` ruby
629
- class TwitterAPI < Grape::API
630
-
631
- version 'v1'
632
-
633
- desc "Retrieves the API version number."
634
- get "version" do
635
- api.version
636
- end
637
-
638
- desc "Reverses a string.", { :params =>
639
- { "s" => { :desc => "string to reverse", :type => "string" }}
640
- }
641
- get "reverse" do
642
- params[:s].reverse
643
- end
644
- end
645
- ```
784
+ Grape routes can be reflected at runtime. This can notably be useful for generating
785
+ documentation.
646
786
 
647
- Grape then exposes arrays of API versions and compiled routes. Each route
787
+ Grape exposes arrays of API versions and compiled routes. Each route
648
788
  contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`,
649
789
  `route_path` and `route_params`. The description and the optional hash that
650
790
  follows the API path may contain any number of keys and its values are also
@@ -654,56 +794,20 @@ accessible via dynamically-generated `route_[name]` functions.
654
794
  TwitterAPI::versions # yields [ 'v1', 'v2' ]
655
795
  TwitterAPI::routes # yields an array of Grape::Route objects
656
796
  TwitterAPI::routes[0].route_version # yields 'v1'
657
- TwitterAPI::routes[0].route_description # yields [ { "s" => { :desc => "string to reverse", :type => "string" }} ]
797
+ TwitterAPI::routes[0].route_description # etc.
658
798
  ```
659
799
 
660
- Parameters can also be tagged to the method declaration itself.
661
-
662
- ``` ruby
663
- class StringAPI < Grape::API
664
- get "split/:string", { :params => { "token" => "a token" }, :optional_params => { "limit" => "the limit" } } do
665
- params[:string].split(params[:token], (params[:limit] || 0))
666
- end
667
- end
668
-
669
- StringAPI::routes[0].route_params # yields a hash {"string" => "", "token" => "a token"}
670
- StringAPI::routes[0].route_optional_params # yields a hash {"limit" => "the limit"}
671
- ```
672
-
673
- It's possible to retrieve the information about the current route from within an API call with `route`.
800
+ It's possible to retrieve the information about the current route from within an API
801
+ call with `route`.
674
802
 
675
803
  ``` ruby
676
804
  class MyAPI < Grape::API
677
- desc "Returns a description of a parameter.", { :params => { "id" => "a required id" } }
678
- get "params/:id" do
679
- route.route_params[params[:id]] # returns "a required id"
680
- end
681
- end
682
- ```
683
-
684
- You can use this information to create a helper that will check if the request has
685
- all required parameters:
686
-
687
- ``` ruby
688
- class MyAPI < Grape::API
689
-
690
- helpers do
691
- def validate_request!
692
- # skip validation if no parameter is declared
693
- return unless route.route_params
694
- route.route_params.each do |k, v|
695
- if !params.has_key? k
696
- error!("Missing field: #{k}", 400)
697
- end
698
- end
699
- end
805
+ desc "Returns a description of a parameter."
806
+ params do
807
+ requires :id, :type => Integer, :desc => "Identity."
700
808
  end
701
-
702
- before { validate_request! }
703
-
704
- desc "creates a new item resource", :params => { :name => 'name is a required parameter' }
705
- post :items do
706
- ...
809
+ get "params/:id" do
810
+ route.route_params[params[:id]] # yields the parameter description
707
811
  end
708
812
  end
709
813
  ```
@@ -712,12 +816,10 @@ end
712
816
 
713
817
  Grape by default anchors all request paths, which means that the request URL
714
818
  should match from start to end to match, otherwise a `404 Not Found` is
715
- returned.
716
- However, this is sometimes not what you want, because it is not always known up
717
- front what can be expected from the call.
718
- This is because Rack-mount by default anchors requests to match from the start
719
- to the end, or not at all. Rails solves this problem by using a `:anchor =>
720
- false` option in your routes.
819
+ returned. However, this is sometimes not what you want, because it is not always
820
+ 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.
822
+ Rails solves this problem by using a `:anchor => false` option in your routes.
721
823
  In Grape this option can be used as well when a method is defined.
722
824
 
723
825
  For instance when you're API needs to get part of an URL, for instance:
@@ -739,13 +841,87 @@ specification and using the `PATH_INFO` Rack environment variable, using
739
841
  `env["PATH_INFO"]`. This will hold everything that comes after the '/urls/'
740
842
  part.
741
843
 
742
- ## Note on Patches/Pull Requests
844
+ ## Writing Tests
845
+
846
+ You can test a Grape API with RSpec by making HTTP requests and examining the response.
847
+
848
+ ### Writing Tests with Rack
849
+
850
+ Use `rack-test` and define your API as `app`.
851
+
852
+ ```ruby
853
+ require 'spec_helper'
854
+
855
+ describe Twitter::API do
856
+ include Rack::Test::Methods
857
+
858
+ def app
859
+ Twitter::API
860
+ end
861
+
862
+ describe Twitter::API do
863
+ describe "GET /api/v1/statuses" do
864
+ it "returns an empty array of statuses" do
865
+ get "/api/v1/statuses"
866
+ last_response.status.should == 200
867
+ JSON.parse(response.body).should == []
868
+ end
869
+ end
870
+ describe "GET /api/v1/statuses/:id" do
871
+ it "returns a status by id" do
872
+ status = Status.create!
873
+ get "/api/v1/statuses/#{status.id}"
874
+ last_response.body.should == status.to_json
875
+ end
876
+ end
877
+ end
878
+ end
879
+ ```
880
+
881
+ ### Writing Tests with Rails
882
+
883
+ ``` ruby
884
+ require 'spec_helper'
885
+
886
+ describe Twitter::API do
887
+ describe "GET /api/v1/statuses" do
888
+ it "returns an empty array of statuses" do
889
+ get "/api/v1/statuses"
890
+ response.status.should == 200
891
+ JSON.parse(response.body).should == []
892
+ end
893
+ end
894
+ describe "GET /api/v1/statuses/:id" do
895
+ it "returns a status by id" do
896
+ status = Status.create!
897
+ get "/api/v1/statuses/#{status.id}"
898
+ resonse.body.should == status.to_json
899
+ end
900
+ end
901
+ end
902
+ ```
903
+
904
+ In Rails, HTTP request tests would go into the `spec/request` group. You may want your API code to go into
905
+ `app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
906
+
907
+ ```ruby
908
+ RSpec.configure do |config|
909
+ config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
910
+ :file_path => /spec\/api/
911
+ }
912
+ end
913
+ ```
914
+
915
+ ## Contributing to Grape
916
+
917
+ Grape is work of dozens of contributors. You're encouraged to submit pull requests, propose
918
+ features and discuss issues.
743
919
 
744
920
  * Fork the project
745
921
  * Write tests for your new feature or a test that reproduces a bug
746
922
  * Implement your feature or make a bug fix
747
923
  * Do not mess with Rakefile, version or history
748
- * Commit, push and make a pull request. Bonus points for topical branches.
924
+ * Commit, push and make a pull request. Bonus points for topic branches.
749
925
 
750
926
  ## License
751
927
 
@@ -753,5 +929,4 @@ MIT License. See LICENSE for details.
753
929
 
754
930
  ## Copyright
755
931
 
756
- Copyright (c) 2010-2012 Michael Bleigh and Intridea, Inc.
757
-
932
+ Copyright (c) 2010-2012 Michael Bleigh, and Intridea, Inc.