Skip to content

Commit c050645

Browse files
author
Ben Rosenblum
committed
Merge branch 'master' of https://github.com/intridea/grape
Conflicts: CHANGELOG.markdown
2 parents 04149be + 20b5321 commit c050645

File tree

10 files changed

+453
-131
lines changed

10 files changed

+453
-131
lines changed

CHANGELOG.markdown

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
Next Release
2-
============
1+
0.2.1 (7/11/2012)
2+
=================
33

44
* [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
5+
* [#186](https://github.com/intridea/grape/issues/186): Fix: helpers allow multiple calls with modules and blocks - [@ppadron](https://github.com/ppadron).
6+
* [#188](https://github.com/intridea/grape/pull/188): Fix: multi-method routes append '(.:format)' only once - [@kainosnoema](https://github.com/kainosnoema).
57
* [#64](https://github.com/intridea/grape/issues/64), [#180](https://github.com/intridea/grape/pull/180): Added support to get request bodies as parameters - [@bobbytables](https://github.com/bobbytables).
68
* [#175](https://github.com/intridea/grape/pull/175): Added support for API versioning based on a request parameter - [@jackcasey](https://github.com/jackcasey).
79
* [#168](https://github.com/intridea/grape/pull/168): Fix: Formatter can parse symbol keys in the headers hash - [@netmask](https://github.com/netmask).
810
* [#169](https://github.com/intridea/grape/pull/169): Silence multi_json deprecation warnings - [@whiteley](https://github.com/whiteley).
911
* [#166](https://github.com/intridea/grape/pull/166): Added support for `redirect`, including permanent and temporary - [@allenwei](https://github.com/allenwei).
1012
* [#159](https://github.com/intridea/grape/pull/159): Added `:requirements` to routes, allowing to use reserved characters in paths - [@gaiottino](https://github.com/gaiottino).
1113
* [#156](https://github.com/intridea/grape/pull/156): Added support for adding formatters to entities - [@bobbytables](https://github.com/bobbytables).
14+
* [#183](https://github.com/intridea/grape/pull/183): Added ability to include documentation in entities - [@flah00](https://github.com/flah00).
15+
* [#189](https://github.com/intridea/grape/pull/189): `HEAD` requests no longer return a body - [@stephencelis](https://github.com/stephencelis).
16+
* [#97](https://github.com/intridea/grape/issues/97): Allow overriding `Content-Type` - [@dblock](https://github.com/dblock).
1217

1318
0.2.0 (3/28/2012)
1419
=================

README.markdown

Lines changed: 213 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,18 @@ class Twitter::API < Grape::API
7777
end
7878
```
7979

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+
end
87+
```
88+
8089
## Mounting
8190

82-
The above sample creates a Rack application that can be run from a rackup *config.ru* file
91+
The above sample creates a Rack application that can be run from a rackup *config.ru* file
8392
with `rackup`:
8493

8594
``` ruby
@@ -119,7 +128,7 @@ There are three strategies in which clients can reach your API's endpoints: `:he
119128
version 'v1', :using => :header
120129
```
121130

122-
Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
131+
Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
123132

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

@@ -138,15 +147,15 @@ Using this versioning strategy, clients should pass the desired version in the U
138147

139148
curl -H http://localhost:9292/v1/statuses/public_timeline
140149

141-
Serialization takes place automatically.
150+
Serialization takes place automatically.
142151

143152
### Param
144153

145154
```ruby
146155
version 'v1', :using => :param
147156
```
148157

149-
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.
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.
150159

151160
curl -H http://localhost:9292/events?apiver=v1
152161

@@ -160,7 +169,7 @@ version 'v1', :using => :param, :parameter => "v"
160169

161170
## Parameters
162171

163-
Parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
172+
Parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
164173
along with any named parameters you specify in your route strings.
165174

166175
```ruby
@@ -345,6 +354,51 @@ class Twitter::API < Grape::API
345354
end
346355
```
347356

357+
## Logging
358+
359+
`Grape::API` provides a `logger` method which by default will return an instance of the `Logger`
360+
class from Ruby's standard library.
361+
362+
To log messages from within an endpoint, you need to define a helper to make the logger
363+
available in the endpoint context:
364+
365+
``` ruby
366+
class API < Grape::API
367+
helpers do
368+
def logger
369+
API.logger
370+
end
371+
end
372+
get '/hello' do
373+
logger.info "someone said hello"
374+
"hey there"
375+
end
376+
end
377+
```
378+
379+
You can also set your own logger:
380+
381+
``` ruby
382+
class MyLogger
383+
def warning(message)
384+
puts "this is a warning: #{message}"
385+
end
386+
end
387+
388+
class API < Grape::API
389+
logger MyLogger.new
390+
helpers do
391+
def logger
392+
API.logger
393+
end
394+
end
395+
get '/hello' do
396+
logger.warning "someone said hello"
397+
"hey there"
398+
end
399+
end
400+
```
401+
348402
## Content-Types
349403

350404
By default, Grape supports _XML_, _JSON_, _Atom_, _RSS_, and _text_ content-types.
@@ -372,9 +426,20 @@ class Twitter::API < Grape::API
372426
end
373427
```
374428

429+
You can override the content-type by setting the `Content-Type` header.
430+
431+
``` ruby
432+
class API < Grape::API
433+
get '/script' do
434+
content_type "application/javascript"
435+
"var x = 1;"
436+
end
437+
end
438+
```
439+
375440
## Writing Tests
376441

377-
You can test a Grape API with RSpec by making HTTP requests and examining the response.
442+
You can test a Grape API with RSpec by making HTTP requests and examining the response.
378443

379444
### Writing Tests with Rack
380445

@@ -443,11 +508,122 @@ RSpec.configure do |config|
443508
end
444509
```
445510

511+
## Reusable Responses with Entities
512+
513+
Entities are a reusable means for converting Ruby objects to API responses.
514+
Entities can be used to conditionally include fields, nest other entities, and build
515+
ever larger responses, using inheritance.
516+
517+
### Defining Entities
518+
519+
Entities inherit from Grape::Entity, and define a simple DSL. Exposures can use
520+
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
524+
array.
525+
526+
* `expose SYMBOLS`
527+
* define a list of fields which will always be exposed
528+
* `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}`
532+
* expose a value, formatting it first
533+
* :format_with can only be applied to one exposure at a time
534+
* `expose SYMBOL, {:as => "alias"}`
535+
* Expose a value, changing its hash key from SYMBOL to alias
536+
* :as can only be applied to one exposure at a time
537+
* `expose SYMBOL BLOCK`
538+
* block arguments are object and options
539+
* expose the value returned by the block
540+
* block can only be applied to one exposure at a time
541+
542+
``` ruby
543+
module API
544+
module Entities
545+
class User < Grape::Entity
546+
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(' ')}
551+
expose :latest_status, :using => API::Status, :as => :status
552+
end
553+
end
554+
end
555+
556+
module API
557+
module Entities
558+
class UserDetailed < API::Entities::User
559+
expose :account_id
560+
end
561+
end
562+
end
563+
```
564+
565+
### Using Entities
566+
567+
Once an entity is defined, it can be used within endpoints, by calling #present. The #present
568+
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.
570+
571+
If the entity includes documentation it can be included in an endpoint's description.
572+
573+
``` ruby
574+
module API
575+
class Users < Grape::API
576+
version 'v1'
577+
578+
desc 'User index', {
579+
:object_fields => API::Entities::User.documentation
580+
}
581+
get '/users' do
582+
@users = User.all
583+
type = current_user.admin? ? :full : :default
584+
present @users, with: API::Entities::User, :type => type
585+
end
586+
end
587+
end
588+
```
589+
590+
### Caveats
591+
592+
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.
595+
596+
```ruby
597+
module API
598+
module Entities
599+
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"}
602+
end
603+
end
604+
end
605+
```
606+
607+
This can be problematic, when you have mixed collections. Using #respond_to? is safer.
608+
609+
```ruby
610+
module API
611+
module Entities
612+
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)}
616+
end
617+
end
618+
end
619+
```
620+
446621
## Describing and Inspecting an API
447622

448623
Grape lets you add a description to an API along with any other optional
449624
elements that can also be inspected at runtime.
450-
This can be useful for generating documentation.
625+
This can be useful for generating documentation. If the response
626+
requires documentation, consider using an entity.
451627

452628
``` ruby
453629
class TwitterAPI < Grape::API
@@ -485,13 +661,13 @@ Parameters can also be tagged to the method declaration itself.
485661

486662
``` ruby
487663
class StringAPI < Grape::API
488-
get "split/:string", { :params => [ "token" ], :optional_params => [ "limit" ] } do
664+
get "split/:string", { :params => { "token" => "a token" }, :optional_params => { "limit" => "the limit" } } do
489665
params[:string].split(params[:token], (params[:limit] || 0))
490666
end
491667
end
492668

493-
StringAPI::routes[0].route_params # yields an array [ "string", "token" ]
494-
StringAPI::routes[0].route_optional_params # yields an array [ "limit" ]
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"}
495671
```
496672

497673
It's possible to retrieve the information about the current route from within an API call with `route`.
@@ -505,6 +681,33 @@ class MyAPI < Grape::API
505681
end
506682
```
507683

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
700+
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+
...
707+
end
708+
end
709+
```
710+
508711
## Anchoring
509712

510713
Grape by default anchors all request paths, which means that the request URL

lib/grape/api.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,14 @@ def represent(model_class, options)
208208
# end
209209
# end
210210
# end
211-
def helpers(mod = nil, &block)
212-
if block_given? || mod
213-
mod ||= settings.peek[:helpers] || Module.new
211+
def helpers(new_mod = nil, &block)
212+
if block_given? || new_mod
213+
mod = settings.peek[:helpers] || Module.new
214+
if new_mod
215+
mod.class_eval do
216+
include new_mod
217+
end
218+
end
214219
mod.class_eval &block if block_given?
215220
set(:helpers, mod)
216221
else

lib/grape/endpoint.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ def prepare_path(path)
8585
parts << ':version' if settings[:version] && settings[:version_options][:using] == :path
8686
parts << namespace.to_s if namespace
8787
parts << path.to_s if path && '/' != path
88-
parts.last << '(.:format)'
89-
Rack::Mount::Utils.normalize_path(parts.join('/'))
88+
Rack::Mount::Utils.normalize_path(parts.join('/') + '(.:format)')
9089
end
9190

9291
def namespace
@@ -153,7 +152,7 @@ def error!(message, status=403)
153152
end
154153

155154
# Redirect to a new url.
156-
#
155+
#
157156
# @param url [String] The url to be redirect.
158157
# @param options [Hash] The options used when redirect.
159158
# :permanent, default true.
@@ -164,7 +163,7 @@ def redirect(url, options = {})
164163
else
165164
if env['HTTP_VERSION'] == 'HTTP/1.1' && request.request_method.to_s.upcase != "GET"
166165
status 303
167-
else
166+
else
168167
status 302
169168
end
170169
end
@@ -198,7 +197,12 @@ def header(key = nil, val = nil)
198197
@header
199198
end
200199
end
201-
200+
201+
# Set response content-type
202+
def content_type(val)
203+
header('Content-Type', val)
204+
end
205+
202206
# Set or get a cookie
203207
#
204208
# @example
@@ -297,6 +301,7 @@ def run(env)
297301
def build_middleware
298302
b = Rack::Builder.new
299303

304+
b.use Rack::Head
300305
b.use Grape::Middleware::Error,
301306
:default_status => settings[:default_error_status] || 403,
302307
:rescue_all => settings[:rescue_all],

0 commit comments

Comments
 (0)