Skip to content

Commit 65333c6

Browse files
committed
Merge pull request ruby-grape#143 from dblock/frontier-mount-docs
Frontier: Updated README on versioning, mounting, etc.
2 parents a449fd4 + 4e0eaac commit 65333c6

File tree

2 files changed

+107
-23
lines changed

2 files changed

+107
-23
lines changed

README.markdown

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,25 @@ class Twitter::API < Grape::API
7474
end
7575

7676
resource :account do
77-
before{ authenticate! }
77+
before { authenticate! }
7878

7979
get '/private' do
8080
"Congratulations, you found the secret!"
8181
end
8282
end
83+
8384
end
8485
```
8586

8687
## Mounting
8788

88-
The above sample creates a Rack application that can be run from a rackup *config.ru* file:
89+
The above sample creates a Rack application that can be run from a rackup *config.ru* file
90+
with `rackup`:
8991

9092
``` ruby
9193
run Twitter::API
9294
```
95+
9396
And would respond to the following routes:
9497

9598
GET /statuses/public_timeline(.json)
@@ -103,12 +106,23 @@ In a Rails application, modify *config/routes*:
103106
mount Twitter::API => "/"
104107
```
105108

109+
You can mount multiple API implementations inside another one.
110+
111+
```ruby
112+
class Twitter::API < Grape::API
113+
mount Twitter::APIv1
114+
mount Twitter::APIv2
115+
end
116+
```
117+
106118
## Versioning
107119

108-
Versioning is handled with HTTP Accept head by default, but can be configures
109-
to [use different strategies](https://github.com/intridea/grape/wiki/API-Versioning).
110-
For example, to request the above with a version, you would make the following
111-
request:
120+
There are two stragies in which clients can reach your API's endpoints: `:header`
121+
and `:path`. The default strategy is `:header`.
122+
123+
version 'v1', :using => :header
124+
125+
Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
112126

113127
curl -H Accept=application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
114128

@@ -117,7 +131,35 @@ supplied. This behavior is similar to routing in Rails. To circumvent this defau
117131
one could use the `:strict` option. When this option is set to `true`, a `404 Not found` error
118132
is returned when no correct Accept header is supplied.
119133

120-
Serialization takes place automatically.
134+
version 'v1', :using => :path
135+
136+
Using this versioning strategy, clients should pass the desired version in the URL.
137+
138+
curl -H http://localhost:9292/v1/statuses/public_timeline
139+
140+
Serialization takes place automatically.
141+
142+
## Parameters
143+
144+
Parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
145+
along with any named parameters you specify in your route strings.
146+
147+
```ruby
148+
get do
149+
Article.order(params[:sort_by])
150+
end
151+
```
152+
153+
## Headers
154+
155+
Headers are available through the `env` hash object.
156+
157+
```ruby
158+
get do
159+
error! 'Unauthorized', 401 unless env['HTTP_SECRET_PASSWORD'] == 'swordfish'
160+
...
161+
end
162+
```
121163

122164
## Helpers
123165

@@ -244,6 +286,8 @@ class Twitter::API < Grape::API
244286
end
245287
```
246288

289+
Or rescue specific exceptions.
290+
247291
``` ruby
248292
class Twitter::API < Grape::API
249293
rescue_from ArgumentError do |e|
@@ -284,20 +328,42 @@ end
284328

285329
## Writing Tests
286330

287-
You can test a Grape API with RSpec. Tests make HTTP requests, therefore they
288-
must go into the `spec/request` group. You may want your API code to go into
289-
`app/api` - you can match that layout under `spec` by adding the following in
290-
`spec/spec_helper.rb`.
331+
You can test a Grape API with RSpec by making HTTP requests and examining the response.
291332

292-
``` ruby
293-
RSpec.configure do |config|
294-
config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
295-
:file_path => /spec\/api/
296-
}
333+
### Writing Tests with Rack
334+
335+
Use `rack-test` and define your API as `app`.
336+
337+
```ruby
338+
require 'spec_helper'
339+
340+
describe Twitter::API do
341+
include Rack::Test::Methods
342+
343+
def app
344+
Twitter::API
345+
end
346+
347+
describe Twitter::API do
348+
describe "GET /api/v1/statuses" do
349+
it "returns an empty array of statuses" do
350+
get "/api/v1/statuses"
351+
last_response.status.should == 200
352+
JSON.parse(response.body).should == []
353+
end
354+
end
355+
describe "GET /api/v1/statuses/:id" do
356+
it "returns a status by id" do
357+
status = Status.create!
358+
get "/api/v1/statuses/#{status.id}"
359+
last_resonse.body.should == status.to_json
360+
end
361+
end
362+
end
297363
end
298364
```
299365

300-
A simple RSpec API test makes a `get` request and parses the response.
366+
### Writing Tests with Rails
301367

302368
``` ruby
303369
require 'spec_helper'
@@ -310,6 +376,24 @@ describe Twitter::API do
310376
JSON.parse(response.body).should == []
311377
end
312378
end
379+
describe "GET /api/v1/statuses/:id" do
380+
it "returns a status by id" do
381+
status = Status.create!
382+
get "/api/v1/statuses/#{status.id}"
383+
resonse.body.should == status.to_json
384+
end
385+
end
386+
end
387+
```
388+
389+
In Rails, HTTP request tests would go into the `spec/request` group. You may want your API code to go into
390+
`app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
391+
392+
```ruby
393+
RSpec.configure do |config|
394+
config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
395+
:file_path => /spec\/api/
396+
}
313397
end
314398
```
315399

spec/grape/api_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,15 +1065,15 @@ class CommunicationError < RuntimeError; end
10651065
end
10661066
it "should force txt without an extension" do
10671067
get '/meaning_of_life'
1068-
last_response.body.should == "{:meaning_of_life=>42}"
1068+
last_response.body.should == { :meaning_of_life => 42 }.to_s
10691069
end
10701070
it "should not force txt with an extension" do
10711071
get '/meaning_of_life.json'
1072-
last_response.body.should == '{"meaning_of_life":42}'
1072+
last_response.body.should == { :meaning_of_life => 42 }.to_json
10731073
end
10741074
it "should force txt from a non-accepting header" do
10751075
get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'application/json' }
1076-
last_response.body.should == "{:meaning_of_life=>42}"
1076+
last_response.body.should == { :meaning_of_life => 42 }.to_s
10771077
end
10781078
end
10791079
context ":json" do
@@ -1085,15 +1085,15 @@ class CommunicationError < RuntimeError; end
10851085
end
10861086
it "should force json without an extension" do
10871087
get '/meaning_of_life'
1088-
last_response.body.should == '{"meaning_of_life":42}'
1088+
last_response.body.should == { :meaning_of_life => 42 }.to_json
10891089
end
10901090
it "should not force json with an extension" do
10911091
get '/meaning_of_life.txt'
1092-
last_response.body.should == "{:meaning_of_life=>42}"
1092+
last_response.body.should == { :meaning_of_life => 42 }.to_s
10931093
end
10941094
it "should force json from a non-accepting header" do
10951095
get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'text/html' }
1096-
last_response.body.should == '{"meaning_of_life":42}'
1096+
last_response.body.should == { :meaning_of_life => 42 }.to_json
10971097
end
10981098
end
10991099
end

0 commit comments

Comments
 (0)