restfulness 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8b7f899ce27987a0fc7cd6bfd1d272e999921b4a
4
- data.tar.gz: 4c37afe53ef267decffc2d72b7fab1b5407bc2ae
3
+ metadata.gz: ce9b42d05b92e2107f89ce16f36a5f1bbfabe6e7
4
+ data.tar.gz: b9cb7bdc241661ab063b45f20a81a967f43eeee6
5
5
  SHA512:
6
- metadata.gz: 099b1309377a645b8fad3c13116aa5e65d87a43dc0caa4987a05e1b7aefce60a6265e6f0911c7728675e99d5bed40ec2b7c9aab5d717618261f45b23413c23b9
7
- data.tar.gz: 0fa3111acd852dfec3d0b3fe14ab02eae9b3288dcd7e32e81da062072f4e6ac3888aebe6b63c958652ea2beac1954d322f16ae60b6ac4d5a185e28cbe0cf7256
6
+ metadata.gz: 7809aad851643407a59b3d376a75d39c4a1f703cf18cc647817e018971434e547dd8fbfcbd6b01e6dafb3aa7e510eee7233ca5a75ecf839239b52596e5bac5ca
7
+ data.tar.gz: f8fbe0c48f0a05bd5f50f48ccb56545afed3408ed5d4941ffd7db5303dbb107203b6656de0c7f13cf937bb88ee716dd2b7d8a7499681abdfbdc2492dc3f14206
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
19
  *.swp
20
+ *.swo
@@ -1,9 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.3.0
4
+ - 2.2.2
5
+ - 2.1.5
3
6
  - 2.0.0
4
- - 1.9.3
5
7
  - jruby-19mode # JRuby in 1.9 mode
6
- - rbx # Probably a bit optimistic
7
- matrix:
8
- allow_failures:
9
- - rvm: rbx
8
+ - rbx
9
+ before_install:
10
+ - gem install bundler
data/README.md CHANGED
@@ -6,93 +6,97 @@ Because REST APIs are all about resources, not routes.
6
6
 
7
7
  ## Introduction
8
8
 
9
- Restfulness is an attempt to create a Ruby library that helps create truly REST based APIs to your services. The focus is placed on performing HTTP actions on resources via specific routes, as opposed to the current convention of assigning routes and HTTP actions to methods or blocks of code. The difference is subtle, but makes for a much more natural approach to building APIs.
9
+ Restfulness is a simple Ruby library for creating REST APIs. Each endpoint defined in the routing configuration refers to a resource class containing HTTP actions and callbacks. When an HTTP request is received, the callbacks are checked, and the appropriate action is called to provide a response.
10
10
 
11
- The current version is very minimal, as it only support JSON content types, and does not have more advanced commonly used HTTP features like sessions or cookies. For most APIs this should be sufficient.
11
+ When creating Restfulness, we had a set of objectives we wanted to achieve:
12
12
 
13
- To try and highlight the diferences between Restfulness and other libraries, lets have a look at a couple of examples.
13
+ * A true "resource" orientated interface.
14
+ * Simple routing.
15
+ * Fast.
16
+ * JSON only responses.
17
+ * Take advantage of HTTP flow control using callbacks.
18
+ * Simple error handling, and "instant abort" exceptions.
14
19
 
15
- [Grape](https://github.com/intridea/grape) is a popular library for creating APIs in a "REST-like" manor. Here is a simplified section of code from their site:
20
+ Here's a code example of what the restfulness side of a rack application might look like:
16
21
 
17
22
  ```ruby
18
- module Twitter
19
- class API < Grape::API
20
-
21
- version 'v1', using: :header, vendor: 'twitter'
22
- format :json
23
-
24
- resource :statuses do
25
-
26
- desc "Return a public timeline."
27
- get :public_timeline do
28
- Status.limit(20)
29
- end
30
-
31
- desc "Return a personal timeline."
32
- get :home_timeline do
33
- authenticate!
34
- current_user.statuses.limit(20)
35
- end
36
-
37
- desc "Return a status."
38
- params do
39
- requires :id, type: Integer, desc: "Status id."
40
- end
41
- route_param :id do
42
- get do
43
- Status.find(params[:id])
44
- end
23
+ # The API definition, this matches incoming request paths to resources
24
+ class TwitterAPI < Restfullness::Application
25
+ routes do
26
+ scope 'api' do
27
+ add 'task', Tasks::ItemResource
28
+ scope 'tasks' do
29
+ add 'public', Tasks::PublicResource
30
+ add 'private', Tasks::PrivateResource
45
31
  end
46
-
47
32
  end
48
-
49
33
  end
50
34
  end
51
35
 
52
- ```
36
+ # Modules are always a good idea to group resources
37
+ module Tasks
38
+ # A simple resource for returning tasks
39
+ class ItemResource < Restfulness::Resource
40
+ # Callback to see if task exsists
41
+ def exists?
42
+ !!task
43
+ end
53
44
 
54
- The focus in Grape is to construct an API by building up a route hierarchy where each HTTP action is tied to a specific ruby block. Resources are mentioned, but they're used more for structure or route-seperation, than a meaningful object.
45
+ # Provide the task, if the #exists? call worked
46
+ def get
47
+ task
48
+ end
55
49
 
56
- Restfulness takes a different approach. The following example attempts to show how you might provide a similar API:
50
+ # Create a new task, this will bypass the #exits? call
51
+ def post
52
+ Task.create(request.params)
53
+ end
57
54
 
58
- ```ruby
59
- class TwitterAPI < Restfullness::Application
60
- routes do
61
- add 'status', StatusResource
62
- scope 'timeline' do
63
- add 'public', Timelines::PublicResource
64
- add 'home', Timelines::HomeResource
55
+ # Update the task, and raise an error with error response if failed
56
+ def patch
57
+ task.update_attributes(request.params) || forbidden!(task.errors)
65
58
  end
66
- end
67
- end
68
59
 
69
- class StatusResource < Restfulness::Resource
70
- def get
71
- Status.find(request.path[:id])
60
+ protected
61
+
62
+ def task
63
+ @task ||= Task.find(request.path[:id])
64
+ end
72
65
  end
73
- end
74
66
 
75
- module Timelines
67
+ # Simple resource that provides list of public tasks, that may be empty
76
68
  class PublicResource < Restfulness::Resource
77
69
  def get
78
- Status.limit(20)
70
+ Task.public.limit(20)
79
71
  end
80
72
  end
81
73
 
82
- # Authentication requires more cowbell, so assume the ApplicationResource is already defined
83
- class HomeResource < ApplicationResource
74
+ # Authorization requires additional code to authenticate the user
75
+ class PrivateResource < Restfulness::Resource
76
+ # If this fails, abort and return 401 Unauthorized response
84
77
  def authorized?
85
- authenticate!
78
+ !current_user
86
79
  end
80
+
81
+ # Assuming authorized, attept to load tasks
87
82
  def get
88
- current_user.statuses.limit(20)
83
+ current_user.tasks.limit(20)
84
+ end
85
+
86
+ protected
87
+
88
+ # Very simple example of authentication
89
+ def current_user
90
+ @current_user ||= authenticate_with_http_basic do |username, password|
91
+ User.authenticate(username, password)
92
+ end
89
93
  end
90
94
  end
91
95
  end
92
96
 
93
97
  ```
94
98
 
95
- I, for one, welcome our new resource overloads. They're a clear and consise way of separating logic between different classes, so an individual model has nothing to do with a collection of models, even if the same model may be provided in the result set.
99
+ Checkout the rest of this document for more of the details on the api, integration with your existing apps, and additional features.
96
100
 
97
101
 
98
102
  ## Installation
@@ -159,7 +163,7 @@ The aim of routes in Restfulness are to be stupid simple. These are the basic ru
159
163
  * Order is important.
160
164
  * Strings are matched directly.
161
165
  * Symbols match anything, and are accessible as path attributes.
162
- * Every route automically gets an :id parameter at the end, that may or may not have a null value.
166
+ * Every route automatically gets an :id parameter at the end, that may or may not have a null value.
163
167
  * Scopes save repeating shared route array entries.
164
168
 
165
169
  Lets see a few examples:
@@ -204,7 +208,7 @@ end
204
208
 
205
209
  ### Resources
206
210
 
207
- Resources are like Controllers in a Rails project. They handle the basic HTTP actions using methods that match the same name as the action. The result of an action is serialized into a JSON object automatically. The actions supported by a resource are:
211
+ Resources are like controllers in a Rails project. They handle the basic HTTP actions using methods that match the same name as the action. The result of an action is serialized into a JSON object automatically. The actions supported by a resource are:
208
212
 
209
213
  * `get`
210
214
  * `head`
@@ -212,7 +216,7 @@ Resources are like Controllers in a Rails project. They handle the basic HTTP ac
212
216
  * `patch`
213
217
  * `put`
214
218
  * `delete`
215
- * `options` - this is the only action provded by default
219
+ * `options` - this is the only action provided by default
216
220
 
217
221
  When creating your resource, simply define the methods you'd like to use and ensure each has a result:
218
222
 
@@ -286,11 +290,11 @@ end
286
290
 
287
291
  #### I18n in Resources
288
292
 
289
- Restfulness uses the [http_accept_language](https://github.com/iain/http_accept_language) gem to automatically handle the `Accept-Language` header coming in from a client. After trying to make a match between the available locales, it will automatically set the `I18n.locale`. You can access the http_accept_language parser via the `request.http_accept_language` method.
293
+ Restfulness uses the [http_accept_language](https://github.com/iain/http_accept_language) gem to automatically handle the `Accept-Language` header received from a client. After trying to make a match between the available locales, it will automatically set the `I18n.locale`. You can access the http_accept_language parser via the `request.http_accept_language` method.
290
294
 
291
295
  For most APIs this should work great, especially for mobile applications where this header is automatically set by the phone. There may however be situations where you need a bit more control. If a user has a preferred language setting for example.
292
296
 
293
- Resources contain two protected methods that can be overwritten if you need more precise control. This is what they look like in the Restfulness code:
297
+ Resources contain two protected methods that can be overwritten. This is what they look like in the Restfulness code:
294
298
 
295
299
  ```ruby
296
300
  protected
@@ -302,16 +306,15 @@ end
302
306
  def set_locale
303
307
  I18n.locale = locale
304
308
  end
305
- ```
309
+ ```
306
310
 
307
311
  The `Resource#set_locale` method is called before any of the other callbacks are handled. This is important as it allows the locale to be set before returning any translatable error messages.
308
312
 
309
- Most users will probably just want to override the `Resource#locale` method and provide the appropriate locale for the request. If you are using a User object or similar, double check your authentication process as the default `authorized?` method will be called *after* the locale is prepared.
310
-
313
+ Most users will probably just want to override the `Resource#locale` method and provide the appropriate locale for the request. If you are using a User object or similar, double check your authentication process as the default `authorized?` method will be called *after* the locale is prepared so that error responses are always in the requested language.
311
314
 
312
315
  #### Authentication in Resources
313
316
 
314
- Restfulness now provides very basic support for the [HTTP Basic Authentication](http://en.wikipedia.org/wiki/Basic_access_authentication). To use it, simply call the `authenticate_with_http_basic` method in your resource definition.
317
+ Restfulness provides basic support for [HTTP Basic Authentication](http://en.wikipedia.org/wiki/Basic_access_authentication). To use, simply call the `authenticate_with_http_basic` method in your resource definition.
315
318
 
316
319
  Here's an example with the authentication details in the code, you'd obviously want to use something a bit more advanced than this in production:
317
320
 
@@ -332,9 +335,9 @@ def authorized?
332
335
  end
333
336
  ```
334
337
 
335
- We don't yet provide support for Digest authentication, but your contributions would be more than welcome. Checkout the [HttpAuthentication/basic.rb](https://github.com/samlown/restfulness/blob/master/lib/restfulness/http_authentication/basic.rb) source for an example.
338
+ Digest authentication is not currently supported, but your contributions would be more than welcome. Checkout the [HttpAuthentication/basic.rb](blob/master/lib/restfulness/http_authentication/basic.rb) source for an example.
336
339
 
337
- Restfulness doesn't make any provisions for requesting authentication from the client as most APIs don't really need to offer this functionality. You can acheive the same effect however by providing the `WWW-Authenticate` header in the response. For example:
340
+ Restfulness doesn't make any provisions for requesting authentication from the client as in our experience most APIs don't need to offer this functionality. You can achieve the same effect however by providing the `WWW-Authenticate` header in the response. For example:
338
341
 
339
342
  ```ruby
340
343
  def authorized?
@@ -353,12 +356,11 @@ def request_authentication
353
356
  end
354
357
  ```
355
358
 
356
-
357
359
  ### Requests
358
360
 
359
- All resource instances have access to a `Request` object via the `#request` method, much like you'd find in a Rails project. It provides access to the details including in the HTTP request: headers, the request URL, path entries, the query, body and/or parameters.
361
+ All resource instances have access to a `Request` object via the `Resource#request` method, much like you'd find in a Rails project. It provides access to the details including in the HTTP request: headers, the request URL, path entries, the query, body and/or parameters.
360
362
 
361
- Restfulness takes a slightly different approach to handling paths, queries, and parameters. Rails and Sinatra apps will typically mash everything together into a `params` hash. While this is convenient for most use cases, it makes it much more difficult to separate values from different contexts. The effects of this are most noticable if you've ever used Models Backbone.js or similar Javascript library. By default a Backbone Model will provide attributes without a prefix in the POST body, so to be able to differenciate between query, path and body parameters you need to ignore the extra attributes, or hack a part of your code to re-add a prefix.
363
+ Restfulness takes a slightly different approach to handling paths, queries, and parameters, as each has their own independent method. Rails and Sinatra will typically mash everything together into a `params` hash. While this is convenient for use cases involving a browser, it is less useful for APIs when body parameters should only contain attributes of the model managed by the resource. If you've ever used Models from Backbone.js or similar Javascript library you appreciate this. When saving a Model, Backbone.js assumes by default that attributes will be provided without a prefix in the POST body.
362
364
 
363
365
  The following key methods are provided in a request object:
364
366
 
@@ -387,7 +389,13 @@ request.query[:page] # 1
387
389
  request.body # "{'key':'value'}" - string payload
388
390
 
389
391
  # Request params
390
- request.params # {'key' => 'value'} - usually a JSON deserialized object
392
+ request.params # {'key' => 'value'} - usually a JSON de-serialized object
393
+
394
+ # Accept header object (nil if none!)
395
+ request.accept.version # For "Accept: application/vnd.example.api+json;version=3", returns "3"
396
+
397
+ # Content Type object (nil if none!)
398
+ request.content_type.to_s # Something like "application/json"
391
399
  ```
392
400
 
393
401
  ### Logging
@@ -408,7 +416,7 @@ Restfulness.sensitive_params = [:password, :secretkey]
408
416
 
409
417
  ## Error Handling
410
418
 
411
- If you want your application to return anything other than a 200 (or 202) status, you have a couple of options that allow you to send codes back to the client.
419
+ If you'd like your application to return anything other than a 200 (or 202) status, you have a couple of options that allow you to send codes back to the client.
412
420
 
413
421
  One of the easiest approaches is to update the `response` code. Take the following example where we set a 403 response and the model's errors object in the payload:
414
422
 
@@ -470,7 +478,7 @@ end
470
478
 
471
479
  This can be a really nice way to mold your errors into a standard format. All HTTP exceptions generated inside resources will pass through `error!`, even those that a triggered by a callback. It gives a great way to provide your own JSON error payload, or even just resort to a simple string.
472
480
 
473
- The currently built in error methods are:
481
+ The currently built in exception methods are:
474
482
 
475
483
  * `not_modified!`
476
484
  * `bad_request!`
@@ -499,19 +507,19 @@ We're all used to the way Rails projects magically reload files so you don't hav
499
507
 
500
508
  Using Restfulness in Rails is the easiest way to take advantage support reloading.
501
509
 
502
- The recomended approach is to create two directories in your Rails projects `/app` path:
510
+ The recommended approach is to create two directories in your Rails projects `/app` path:
503
511
 
504
512
  * `/app/apis` can be used for defining your API route files, and
505
513
  * `/app/resources` for defining a tree of resource definition files.
506
514
 
507
- Add the two paths to your rails autoloading configuration in `/config/application.rb`, there will already be a sample in your config provided by Rails:
515
+ Add the two paths to your rails auto-loading configuration in `/config/application.rb`, there will already be a sample in your config provided by Rails:
508
516
 
509
517
  ```ruby
510
518
  # Custom directories with classes and modules you want to be autoloadable.
511
519
  config.autoload_paths += %W( #{config.root}/app/resources #{config.root}/app/apis )
512
520
  ```
513
521
 
514
- Your Resource and API files will now be autoloadable from your Rails project. The next step is to update our Rails router to be able to find our API. Modify the `/config/routes.rb` file so that it looks something like the following:
522
+ Your Resource and API files will now be auto-loadable from your Rails project. The next step is to update the Rails router to be able to find our API. Modify the `/config/routes.rb` file so that it includes the mount method call:
515
523
 
516
524
  ```ruby
517
525
  YourRailsApp::Application.routes.draw do
@@ -526,7 +534,7 @@ end
526
534
 
527
535
  ```
528
536
 
529
- You'll see in the code sample that we're only loading the Restfulness API during development. Our recommendation is to use Restfulness as close to Rack as possible and avoid any of the Rails overhead. To support request in production, you'll need to update your `/config.rb` so that it looks something like the following:
537
+ You'll see in the code sample that we're only loading the Restfulness API during development. Our recommendation is to use Restfulness as close to Rack as possible and avoid any of the Rails overhead. To support requests in production, you'll need to update your `/config.rb` so that it looks something like the following:
530
538
 
531
539
  ```ruby
532
540
  # This file is used by Rack-based servers to start the application.
@@ -546,8 +554,7 @@ Thats all there is to it! You'll now have auto-reloading in Rails, and fast requ
546
554
 
547
555
  ### The Rack Way
548
556
 
549
- If you're using Restfulness as a standalone project, we recommend using a rack extension like [Shotgun](https://github.com/rtomayko/shotgun).
550
-
557
+ If you're using Restfulness as a standalone project, we recommend using a rack extension like [Shotgun](https://github.com/rtomayko/shotgun) to automatically reload on changes.
551
558
 
552
559
  ## Writing Tests
553
560
 
@@ -649,6 +656,7 @@ Restfulness was created by Sam Lown <me@samlown.com> as a solution for building
649
656
  The project is now awesome, thanks to contributions by:
650
657
 
651
658
  * [Adam Williams](https://github.com/awilliams)
659
+ * [Laura Morillo](https://github.com/lauramorillo)
652
660
 
653
661
 
654
662
  ## Caveats and TODOs
@@ -659,10 +667,20 @@ Restfulness is still a work in progress but at Cabify we are using it in product
659
667
  * Support path methods for automatic URL generation.
660
668
  * Support redirect exceptions.
661
669
  * Needs more functional testing.
662
- * Support for before and after filters in resources, although I'm slightly aprehensive about this.
670
+ * Support for before and after filters in resources, although I'm slightly apprehensive about this.
663
671
 
664
672
  ## History
665
673
 
674
+ ### 0.3.3 - January 19, 2016
675
+
676
+ * Basic support for handling large request bodies received as Tempfile (@lauramorillo)
677
+ * Providing human readable payload for invalid JSON.
678
+ * Added support for Accept and Content-Type header handling. (@samlown)
679
+ * Better handling of IO objects from `rack.input`, such as `Puma::NullIO`. (@samlown)
680
+ * Upgrading to latest version of RSpec (@samlown)
681
+ * Adding `request.env` accessor to Rack env (@amuino)
682
+ * Removing support for Ruby 1.9 (@samlown)
683
+
666
684
  ### 0.3.2 - February 9, 2015
667
685
 
668
686
  * Added support for application/x-www-form-urlencoded parameter decoding (@samlown)
@@ -20,6 +20,9 @@ require "restfulness/resources/authentication"
20
20
  require "restfulness/requests/authorization"
21
21
  require "restfulness/requests/authorization_header"
22
22
 
23
+ require "restfulness/headers/media_type"
24
+ require "restfulness/headers/accept"
25
+
23
26
  require "restfulness/application"
24
27
  require "restfulness/dispatcher"
25
28
  require "restfulness/exceptions"
@@ -22,6 +22,7 @@ module Restfulness
22
22
 
23
23
  request = Request.new(app)
24
24
 
25
+ request.env = env # Reference to Rack env
25
26
  request.uri = rack_req.url
26
27
  request.action = parse_action(env, rack_req.request_method)
27
28
  request.body = rack_req.body
@@ -0,0 +1,66 @@
1
+ module Restfulness
2
+ module Headers
3
+
4
+ # The Accept header handler provides an array of Media Types that the
5
+ # client is willing to accept.
6
+ #
7
+ # Based on a simplified RFC2616 implementation, each media type is stored
8
+ # and ordered.
9
+ #
10
+ # Restfulness does not currently deal with the special 'q' paremeter defined
11
+ # in the standard as quality is not something APIs normally need to handle.
12
+ #
13
+ # Aside from media type detection, a useful feature of the accept header is to
14
+ # provide the desired version of content to provide in the response. This class
15
+ # offers a helper method that will attempt to determine the version.
16
+ #
17
+ # Given the HTTP header:
18
+ #
19
+ # Accept: application/com.example.api+json;version=1
20
+ #
21
+ # The resource instace has access to the version via:
22
+ #
23
+ # request.accept.version == "1"
24
+ #
25
+ class Accept
26
+
27
+ # The -ordered- array of media types provided in the headers
28
+ attr_accessor :media_types
29
+
30
+ def initialize(str = "")
31
+ self.media_types = []
32
+ parse(str) unless str.empty?
33
+ end
34
+
35
+ def parse(str)
36
+ types = str.split(',').map{|t| t.strip}
37
+
38
+ # Attempt to crudely determine order based on length, and store
39
+ types.sort{|a,b| b.length <=> a.length}.each do |t|
40
+ media_types << MediaType.new(t)
41
+ end
42
+ end
43
+
44
+ # Request the version, always assumes that the first media type is the most relevant
45
+ def version
46
+ media_types.first.version
47
+ end
48
+
49
+ def json?
50
+ media_types.each do |mt|
51
+ return true if mt.json?
52
+ end
53
+ false
54
+ end
55
+
56
+ def xml?
57
+ media_types.each do |mt|
58
+ return true if mt.xml?
59
+ end
60
+ false
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end