restfulness 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ddaa3efca8063cce4459e78242665f30c7aa7beb
4
+ data.tar.gz: 39fc6f40248c0a0453ce566fc97537920977e529
5
+ SHA512:
6
+ metadata.gz: 205f618a6e3d7968e9e365e5a90abbbe335ebc994179da9030bc6d57d2bcafacfa9b001f612beea36490e18a2ed8344a2e19e772d70071b266500ba31832b7ec
7
+ data.tar.gz: f2c6c07d14351f6f05e270f34df4b23b08dea402a82b0988cc8ed4076d960d379b88c1bc2384ebcfcb9b7b80c372d681a790558d40b241b09bd5c3fea4ca9a11
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in restfulness.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sam Lown
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,342 @@
1
+ # Restfulness
2
+
3
+ Because REST APIs are all about resources, not routes.
4
+
5
+ ## Introduction
6
+
7
+ 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.
8
+
9
+ 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.
10
+
11
+ To try and highlight the diferences between Restfulness and other libraries, lets have a look at a couple of examples.
12
+
13
+ [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:
14
+
15
+ ```ruby
16
+ module Twitter
17
+ class API < Grape::API
18
+
19
+ version 'v1', using: :header, vendor: 'twitter'
20
+ format :json
21
+
22
+ resource :statuses do
23
+
24
+ desc "Return a public timeline."
25
+ get :public_timeline do
26
+ Status.limit(20)
27
+ end
28
+
29
+ desc "Return a personal timeline."
30
+ get :home_timeline do
31
+ authenticate!
32
+ current_user.statuses.limit(20)
33
+ end
34
+
35
+ desc "Return a status."
36
+ params do
37
+ requires :id, type: Integer, desc: "Status id."
38
+ end
39
+ route_param :id do
40
+ get do
41
+ Status.find(params[:id])
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
49
+
50
+ ```
51
+
52
+ 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.
53
+
54
+ Restfulness takes a different approach. The following example attempts to show how you might provide a similar API:
55
+
56
+ ```ruby
57
+ class TwitterAPI < Restfullness::Application
58
+ routes do
59
+ add 'status', StatusResource
60
+ add 'timeline', 'public', PublicTimelineResource
61
+ add 'timeline', 'home', HomeTimelineResource
62
+ end
63
+ end
64
+
65
+ class StatusResource < Restfulness::Resource
66
+ def get
67
+ Status.find(request.path[:id])
68
+ end
69
+ end
70
+
71
+ class PublicTimelineResource < Restfulness::Resource
72
+ def get
73
+ Status.limit(20)
74
+ end
75
+ end
76
+
77
+ # Authentication requires more cowbell, so assume the ApplicationResource is already defined
78
+ class HomeTimelineResource < ApplicationResource
79
+ def authorized?
80
+ authenticate!
81
+ end
82
+ def get
83
+ current_user.statuses.limit(20)
84
+ end
85
+ end
86
+
87
+ ```
88
+
89
+ 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.
90
+
91
+
92
+ ## Installation
93
+
94
+ Add this line to your application's Gemfile:
95
+
96
+ gem 'restfulness'
97
+
98
+ And then execute:
99
+
100
+ $ bundle
101
+
102
+ Or install it yourself as:
103
+
104
+ $ gem install restfulness
105
+
106
+ ## Usage
107
+
108
+ ### Defining an Application
109
+
110
+ A Restfulness application is a Rack application whose main function is to define the routes that will forward requests on a specific path to a resource. Your applications inherit from the `Restfulness::Application` class. Here's a simple example:
111
+
112
+ ```ruby
113
+ class MyAppAPI < Restfulness::Application
114
+ routes do
115
+ add 'project', ProjectResource
116
+ add 'projects', ProjectsResource
117
+ end
118
+ end
119
+
120
+ ```
121
+
122
+ An application is designed to be included in your Rails, Sinatra, or other Rack project, simply include a new instance of your application in the `config.ru` file:
123
+
124
+ ```ruby
125
+ run Rack::URLMap.new(
126
+ "/" => MyRailsApp::Application,
127
+ "/api" => MyAppAPI.new
128
+ )
129
+ ```
130
+
131
+ By default, Restfulness comes with a Rack compatible dispatcher, but in the future it might make sense to add others.
132
+
133
+ If you want to run Restfulness standalone, simply create a `config.ru` that will load up your application:
134
+
135
+ ```ruby
136
+ require 'my_app'
137
+ run MyApp.new
138
+ ```
139
+
140
+ You can then run this with rackup:
141
+
142
+ ```
143
+ bundle exec rackup
144
+ ```
145
+
146
+ For an example, checkout the `/example` directory in the source code.
147
+
148
+
149
+ ### Routes
150
+
151
+ The aim of routes in Restfulness are to be stupid simple. These are the basic rules:
152
+
153
+ * Each route is an array that forms a path when joined with `/`.
154
+ * Order is important.
155
+ * Strings are matched directly.
156
+ * Symbols match anything, and are accessible as path attributes.
157
+ * Every route automically gets an :id parameter at the end, that may or may not have a null value.
158
+
159
+ Lets see a few examples:
160
+
161
+ ```ruby
162
+ routes do
163
+ # Simple route to access a project, access with:
164
+ # * PUT /project
165
+ # * GET /project/1234
166
+ add 'project', ProjectResource
167
+
168
+ # Parameters are also supported.
169
+ # Access the project id using `request.path[:project_id]`
170
+ add 'project', :project_id, 'status', ProjectStatusResource
171
+ end
172
+ ```
173
+
174
+
175
+ ### Resources
176
+
177
+ 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:
178
+
179
+ * `get`
180
+ * `head`
181
+ * `post`
182
+ * `put`
183
+ * `delete`
184
+ * `options` - this is the only action provded by default
185
+
186
+ When creating your resource, simply define the methods you'd like to use and ensure each has a result:
187
+
188
+ ```ruby
189
+ class ProjectResource < Restfulness::Resource
190
+ # Return the basic object
191
+ def get
192
+ project
193
+ end
194
+
195
+ # Update the object
196
+ def put
197
+ project.update(params)
198
+ end
199
+
200
+ protected
201
+
202
+ def project
203
+ @project ||= Project.find(request.path[:id])
204
+ end
205
+ end
206
+ ```
207
+
208
+ Checking which methods are available is also possible by sending an `OPTIONS` action. Using the above resource as a base:
209
+
210
+ curl -v -X OPTIONS http://localhost:9292/project
211
+
212
+ Will include an `Allow` header that lists: "GET, PUT, OPTIONS".
213
+
214
+ Resources also have support for simple set of built-in callbacks. These have similar objectives to the callbacks used in [Ruby Webmachine](https://github.com/seancribbs/webmachine-ruby) that control the flow of the application using HTTP events.
215
+
216
+ The supported callbacks are:
217
+
218
+ * `exists?` - True by default, not called in create actions like POST.
219
+ * `authorized?` - True by default, is the current user valid?
220
+ * `allowed?` - True by default, does the current have access to the resource?
221
+ * `last_modified` - The date of last update on the model, only called for GET and HEAD requests. Validated against the `If-Modified-Since` header.
222
+ * `etag` - Unique identifier for the object, only called for GET and HEAD requests. Validated against the `If-None-Match` header.
223
+
224
+ To use them, simply override the method:
225
+
226
+ ```ruby
227
+ class ProjectResource < Restfulness::Resource
228
+ # Does the project exist? only called in GET request
229
+ def exists?
230
+ !project.nil?
231
+ end
232
+
233
+ # Return a 304 status if the client can used a cached resource
234
+ def last_modified
235
+ project.updated_at.to_s
236
+ end
237
+
238
+ # Return the basic object
239
+ def get
240
+ project
241
+ end
242
+
243
+ # Update the object
244
+ def post
245
+ Project.create(params)
246
+ end
247
+
248
+ protected
249
+
250
+ def project
251
+ @project ||= Project.find(request.path[:id])
252
+ end
253
+ end
254
+ ```
255
+
256
+
257
+ ### Requests
258
+
259
+ 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.
260
+
261
+ 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.
262
+
263
+ The following key methods are provided in a request object:
264
+
265
+ ```ruby
266
+ # A URI object
267
+ request.uri # #<URI::HTTPS:0x00123456789 URL:https://example.com/somepath?x=y>
268
+
269
+ # Basic request path
270
+ request.path.to_s # '/project/123456'
271
+ request.path # ['project', '123456']
272
+ request.path[:id] # '123456'
273
+ request.path[0] # 'project
274
+
275
+ # More complex request path, from route: ['project', :project_id, 'task']
276
+ request.path.to_s # '/project/123456/task/234567'
277
+ request.path # ['project', '123456', 'task', '234567']
278
+ request.path[:id] # '234567'
279
+ require.path[:project_id] # '123456'
280
+ require.path[2] # 'task'
281
+
282
+ # The request query
283
+ request.query # {:page => 1} - Hash with indifferent access
284
+ request.query[:page] # 1
285
+
286
+ # Request body
287
+ request.body # "{'key':'value'}" - string payload
288
+
289
+ # Request params
290
+ request.params # {'key' => 'value'} - usually a JSON deserialized object
291
+ ```
292
+
293
+ ## Caveats and TODOs
294
+
295
+ Restfulness is still very much a work in progress. Here is a list of things that we'd like to improve or fix:
296
+
297
+ * Support for more serializers, not just JSON.
298
+ * Reloading is a PITA (see note below).
299
+ * Needs more functional testing.
300
+ * Support for before and after filters in resources, although I'm slightly aprehensive about this.
301
+
302
+ ## Reloading
303
+
304
+ Reloading is complicated. Unfortunately we're all used to the way Rails projects magically reload changed files so you don't have to restart the server after each change.
305
+
306
+ If you're using Restfulness as a standalone project, we recommend using a rack extension like [Shotgun](https://github.com/rtomayko/shotgun).
307
+
308
+ If you're adding Restfulness to a Rails project, you can take advantage of the `ActionDispatch::Reloader` rack middleware. Simply include it in the application definition:
309
+
310
+ ```ruby
311
+ class MyAPI < Restfulness::Application
312
+ if Rails.env.development?
313
+ middlewares << ActionDispatch::Relaoder
314
+ end
315
+ routes do
316
+ # etc. etc.
317
+ end
318
+ ```
319
+
320
+ We're still working on ways to improve this. If you have any ideas, please send me a pull request!
321
+
322
+ ## Contributing
323
+
324
+ 1. Fork it
325
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
326
+ 3. Write your code and test the socks off it!
327
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
328
+ 5. Push to the branch (`git push origin my-new-feature`)
329
+ 6. Create new Pull Request
330
+
331
+ ## Contributors
332
+
333
+ Restfulness was created by Sam Lown <me@samlown.com> as a solution for building simple APIs at [Cabify](http://www.cabify.com).
334
+
335
+
336
+ ## History
337
+
338
+ ### 0.1.0 - October 16, 2013
339
+
340
+ First release!
341
+
342
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/example/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+
2
+ source 'https://rubygems.org'
3
+
4
+ gem 'restfulness', path: '../'
5
+
data/example/README.md ADDED
@@ -0,0 +1,42 @@
1
+
2
+ # Restfulness Example App
3
+
4
+ Really simple example of a basic app with a couple of resources.
5
+
6
+ ## Preparation
7
+
8
+ Use bundler to make sure all the dependencies are in place:
9
+
10
+ cd example
11
+ bundle install
12
+
13
+ By default, bundler will expect to find the restfulness gem provided in the parent directory.
14
+
15
+ ## Running
16
+
17
+ bundle exec runit
18
+
19
+ ## Testing
20
+
21
+ Curl is your friend!
22
+
23
+ # Get nothing (returns 404)
24
+ curl -v http://localhost:9292/projects
25
+
26
+ # Post a journey
27
+ curl -v -X POST http://localhost:9292/project -H "Content-Type: application/json" -d "{\"id\":\"project1\",\"name\":\"First Project\"}"
28
+
29
+ # Retrieve it
30
+ curl -v http://localhost:9292/project/project1
31
+
32
+ # Get an array of projects
33
+ curl -v http://localhost:9292/projects
34
+
35
+ # Try updating it
36
+ curl -v -X PUT http://localhost:9292/project/project1 -H "Content-Type: application/json" -d "{\"name\":\"First Updated Project\"}"
37
+
38
+ # Finally remove it and check the list is empty
39
+ curl -v -X DELETE http://localhost:9292/project/project1
40
+ curl -v http://localhost:9292/projects
41
+
42
+