rackful 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,6 @@ module Rackful
8
8
 
9
9
  =begin markdown
10
10
  Subclass of {Rack::Request}, augmented for Rackful requests.
11
- @since 0.0.1
12
11
  =end
13
12
  class Request < Rack::Request
14
13
 
@@ -17,7 +16,6 @@ class Request < Rack::Request
17
16
  The resource factory for the current request.
18
17
  @return [#[]]
19
18
  @see Server#initialize
20
- @since 0.0.1
21
19
  =end
22
20
  def resource_factory; self.env['rackful.resource_factory']; end
23
21
  def base_path
@@ -31,18 +29,15 @@ The resource factory for the current request.
31
29
  Similar to the HTTP/1.1 `Content-Location:` header. Contains the canonical path
32
30
  to the requested resource, which may differ from {#path}
33
31
  @return [Path]
34
- @since 0.1.0
35
32
  =end
36
33
  def content_path; self.env['rackful.content_path'] ||= self.path; end
37
34
  =begin markdown
38
35
  Set by {Rackful::Server#call!}
39
36
  @return [Path]
40
- @since 0.1.0
41
37
  =end
42
38
  def content_path= bp; self.env['rackful.content_path'] = bp.to_path; end
43
39
  =begin markdown
44
40
  @return [Path]
45
- @since 0.1.0
46
41
  =end
47
42
  def path; super.to_path; end
48
43
 
@@ -60,7 +55,6 @@ In a multi-threaded server, multiple requests can be handled at one time.
60
55
  This method returns the request object, created (and registered) by
61
56
  {Server#call!}
62
57
  @return [Request]
63
- @since 0.0.1
64
58
  =end
65
59
  def self.current
66
60
  Thread.current[:rackful_request]
@@ -82,7 +76,6 @@ Assert all <tt>If-*</tt> request headers.
82
76
  @see http://tools.ietf.org/html/rfc2616#section-13.3.3 RFC2616, section 13.3.3
83
77
  for details about weak and strong validator comparison.
84
78
  @todo Implement support for the `If-Range:` header.
85
- @since 0.0.1
86
79
  =end
87
80
  def assert_if_headers resource
88
81
  #raise HTTP501NotImplemented, 'If-Range: request header is not supported.' \
@@ -113,11 +106,15 @@ Assert all <tt>If-*</tt> request headers.
113
106
  elsif cond[:unmodified_since]
114
107
  raise HTTP412PreconditionFailed, 'If-Unmodified-Since'
115
108
  elsif cond[:modified_since]
116
- raise HTTP404NotFound
109
+ raise HTTP404NotFound, resource.path
117
110
  end
118
111
  else
119
112
  if cond[:none_match] && self.validate_etag( etag, cond[:none_match] )
120
- raise HTTP412PreconditionFailed, 'If-None-Match'
113
+ if allow_weak
114
+ raise HTTP304NotModified
115
+ else
116
+ raise HTTP412PreconditionFailed, 'If-None-Match'
117
+ end
121
118
  elsif cond[:match] && ! self.validate_etag( etag, cond[:match] )
122
119
  raise HTTP412PreconditionFailed, 'If-Match'
123
120
  elsif cond[:unmodified_since]
@@ -145,7 +142,6 @@ Hash of acceptable media types and their qualities.
145
142
  This method parses the HTTP/1.1 `Accept:` header. If no acceptable media
146
143
  types are provided, an empty Hash is returned.
147
144
  @return [Hash{media_type => quality}]
148
- @since 0.0.1
149
145
  =end
150
146
  def accept
151
147
  @env['rackful.accept'] ||= begin
@@ -172,7 +168,6 @@ Parses the HTTP/1.1 `If-Match:` header.
172
168
  @return [nil, Array<String>]
173
169
  @see http://tools.ietf.org/html/rfc2616#section-14.24 RFC2616, section 14.24
174
170
  @see #if_none_match
175
- @since 0.0.1
176
171
  =end
177
172
  def if_match none = false
178
173
  header = @env["HTTP_IF_#{ none ? 'NONE_' : '' }MATCH"]
@@ -192,7 +187,6 @@ Parses the HTTP/1.1 `If-None-Match:` header.
192
187
  @return [nil, Array<String>]
193
188
  @see http://tools.ietf.org/html/rfc2616#section-14.26 RFC2616, section 14.26
194
189
  @see #if_match
195
- @since 0.0.1
196
190
  =end
197
191
  def if_none_match
198
192
  self.if_match true
@@ -204,7 +198,6 @@ Parses the HTTP/1.1 `If-None-Match:` header.
204
198
  @return [nil, Time]
205
199
  @see http://tools.ietf.org/html/rfc2616#section-14.25 RFC2616, section 14.25
206
200
  @see #if_unmodified_since
207
- @since 0.0.1
208
201
  =end
209
202
  def if_modified_since unmodified = false
210
203
  header = @env["HTTP_IF_#{ unmodified ? 'UN' : '' }MODIFIED_SINCE"]
@@ -222,7 +215,6 @@ Parses the HTTP/1.1 `If-None-Match:` header.
222
215
  @return [nil, Time]
223
216
  @see http://tools.ietf.org/html/rfc2616#section-14.28 RFC2616, section 14.28
224
217
  @see #if_modified_since
225
- @since 0.0.1
226
218
  =end
227
219
  def if_unmodified_since
228
220
  self.if_modified_since true
@@ -241,7 +233,6 @@ Does any of the tags in `etags` match `etag`?
241
233
  @return [Boolean]
242
234
  @see http://tools.ietf.org/html/rfc2616#section-13.3.3 RFC2616 section 13.3.3
243
235
  for details about weak and strong validator comparison.
244
- @since 0.0.1
245
236
  =end
246
237
  def validate_etag etag, etags
247
238
  etag = etag.to_s
@@ -15,7 +15,6 @@ Classes that include this module may implement a method `content_types`
15
15
  for content negotiation. This method must return a Hash of
16
16
  `media-type => quality` pairs.
17
17
  @see Server, ResourceFactory
18
- @since 0.0.1
19
18
  =end
20
19
  module Resource
21
20
 
@@ -23,6 +22,14 @@ module Resource
23
22
  include Rack::Utils
24
23
 
25
24
 
25
+ =begin
26
+ Normally, when a module is included, all the instance methods of the included
27
+ module become available as instance methods to the including module/class. But
28
+ class methods of the included module don't become available as class methods to
29
+ the including class.
30
+
31
+
32
+ =end
26
33
  def self.included(base)
27
34
  base.extend ClassMethods
28
35
  end
@@ -30,21 +37,21 @@ module Resource
30
37
 
31
38
  module ClassMethods
32
39
 
33
-
34
- #Meta-programmer method.
35
- #@example Have your resource rendered in XML and JSON
36
- # class MyResource
37
- # add_serializer MyResource2XML
38
- # add_serializer MyResource2JSON, 0.5
39
- # end
40
- #@param serializer [Serializer]
41
- #@param quality [Float]
42
- #@return [self]
40
+ =begin
41
+ Meta-programmer method.
42
+ @example Have your resource rendered in XML and JSON
43
+ class MyResource
44
+ add_serializer MyResource2XML
45
+ add_serializer MyResource2JSON, 0.5
46
+ end
47
+ @param serializer [Serializer]
48
+ @param quality [Float]
49
+ @return [self]
50
+ =end
43
51
  def add_serializer serializer, quality = 1.0
44
52
  quality = quality.to_f
45
53
  quality = 1.0 if quality > 1.0
46
54
  quality = 0.0 if quality < 0.0
47
- # The single '@' on the following line is on purpose!
48
55
  s = [serializer, quality]
49
56
  serializer::CONTENT_TYPES.each {
50
57
  |content_type|
@@ -55,11 +62,13 @@ module Resource
55
62
 
56
63
 
57
64
  def serializers
65
+ # The single '@' on the following line is on purpose!
58
66
  @rackful_resource_serializers ||= {}
59
67
  end
60
68
 
61
69
 
62
70
  def all_serializers
71
+ # The single '@' on the following line is on purpose!
63
72
  @rackful_resource_all_serializers ||=
64
73
  if self.superclass.respond_to?(:all_serializers)
65
74
  self.superclass.all_serializers.merge( self.serializers ) do
@@ -72,28 +81,36 @@ module Resource
72
81
  end
73
82
 
74
83
 
75
- #Meta-programmer method.
76
- #@example Have your resource accept XML and JSON in `PUT` requests
77
- # class MyResource
78
- # add_parser XML2MyResource, :PUT
79
- # add_parser JSON2MyResource, :PUT
80
- # end
81
- #@param parser [Parser]
82
- #@param method [#to_sym]
83
- #@return [self]
84
+ =begin
85
+ Meta-programmer method.
86
+ @example Have your resource accept XML and JSON in `PUT` requests
87
+ class MyResource
88
+ add_media_type 'text/xml', :PUT
89
+ add_media_type 'application/json', :PUT
90
+ end
91
+ @param [#to_s] media_type
92
+ @param [#to_sym] method
93
+ @return [self]
94
+ =end
84
95
  def add_media_type media_type, method = :PUT
85
96
  method = method.to_sym
86
97
  self.media_types[method] ||= []
87
- self.media_types[method] << media_type
98
+ self.media_types[method] << media_type.to_s
88
99
  self
89
100
  end
90
101
 
91
102
 
103
+ =begin
104
+ @todo Documentation
105
+ =end
92
106
  def media_types
93
107
  @rackful_resource_media_types ||= {}
94
108
  end
95
109
 
96
110
 
111
+ =begin
112
+ @todo Documentation
113
+ =end
97
114
  def all_media_types
98
115
  @rackful_resource_all_media_types ||=
99
116
  if self.superclass.respond_to?(:all_media_types)
@@ -113,7 +130,6 @@ The best media type for the response body, given the current HTTP request.
113
130
  @param require_match [Boolean]
114
131
  @return [String] content-type
115
132
  @raise [HTTP406NotAcceptable] if `require_match` is `true` and no match was found.
116
- @since 0.1.0
117
133
  =end
118
134
  def best_content_type accept, require_match = true
119
135
  if accept.empty?
@@ -142,18 +158,6 @@ The best media type for the response body, given the current HTTP request.
142
158
  end
143
159
 
144
160
 
145
- #~ # @param content_type [String]
146
- #~ # @param method [#to_s]
147
- #~ # @return [Serializer]
148
- #~ def parser request
149
- #~ method = request.request_method.upcase.to_sym
150
- #~ if !parsers[method] || !parsers[method][request.media_type]
151
- #~ raise HTTP415UnsupportedMediaType, ( parsers[method] ? parsers[method].keys : [] )
152
- #~ end
153
- #~ parsers[method][request.media_type].new( request )
154
- #~ end
155
-
156
-
157
161
  end # module ClassMethods
158
162
 
159
163
 
@@ -167,39 +171,33 @@ The best media type for the response body, given the current HTTP request.
167
171
  end
168
172
 
169
173
 
170
- =begin markdown
171
- @!method to_struct()
172
- @return [#to_json, #each_pair]
173
- =end
174
-
174
+ =begin
175
+ @!method do_METHOD( Request, Rack::Response )
176
+ HTTP/1.1 method handler.
175
177
 
176
- # @!method do_METHOD( Request, Rack::Response )
177
- # HTTP/1.1 method handler.
178
- #
179
- # To handle certain HTTP/1.1 request methods, resources must implement methods
180
- # called `do_<HTTP_METHOD>`.
181
- # @example Handling `PATCH` requests
182
- # def do_PATCH request, response
183
- # response['Content-Type'] = 'text/plain'
184
- # response.body = [ 'Hello world!' ]
185
- # end
186
- # @abstract
187
- # @return [void]
188
- # @raise [HTTPStatus, RuntimeError]
189
- # @since 0.0.1
178
+ To handle certain HTTP/1.1 request methods, resources must implement methods
179
+ called `do_<HTTP_METHOD>`.
180
+ @example Handling `PATCH` requests
181
+ def do_PATCH request, response
182
+ response['Content-Type'] = 'text/plain'
183
+ response.body = [ 'Hello world!' ]
184
+ end
185
+ @abstract
186
+ @return [void]
187
+ @raise [HTTPStatus, RuntimeError]
188
+ =end
190
189
 
191
190
 
192
191
  =begin markdown
193
192
  The path of this resource.
194
193
  @return [Rackful::Path]
195
194
  @see #initialize
196
- @since 0.0.1
197
195
  =end
198
- attr_reader :path
196
+ def path; @rackful_resource_path; end
199
197
 
200
198
 
201
199
  def path= path
202
- @path = path.to_s.to_path
200
+ @rackful_resource_path = Path.new(path)
203
201
  end
204
202
 
205
203
 
@@ -214,6 +212,7 @@ The path of this resource.
214
212
  self.path.slashify == Request.current.path.slashify
215
213
  end
216
214
 
215
+
217
216
  =begin markdown
218
217
  Does this resource _exists_?
219
218
 
@@ -223,13 +222,15 @@ produce an empty resource to to handle the `PUT` request. `HEAD` and `GET`
223
222
  requests will still yield `404 Not Found`.
224
223
 
225
224
  @return [Boolean] The default implementation returns `false`.
226
- @since 0.0.1
227
225
  =end
228
226
  def empty?
229
227
  false
230
228
  end
231
229
 
232
230
 
231
+ =begin markdown
232
+
233
+ =end
233
234
  def to_rackful
234
235
  self
235
236
  end
@@ -237,48 +238,46 @@ requests will still yield `404 Not Found`.
237
238
 
238
239
  =begin markdown
239
240
  @!attribute [r] get_etag
240
- The ETag of this resource.
241
+ The ETag of this resource.
241
242
 
242
- If your classes implement this method, then an `ETag:` response
243
- header is generated automatically when appropriate. This allows clients to
244
- perform conditional requests, by sending an `If-Match:` or
245
- `If-None-Match:` request header. These conditions are then asserted
246
- for you automatically.
243
+ If your classes implement this method, then an `ETag:` response
244
+ header is generated automatically when appropriate. This allows clients to
245
+ perform conditional requests, by sending an `If-Match:` or
246
+ `If-None-Match:` request header. These conditions are then asserted
247
+ for you automatically.
247
248
 
248
- Make sure your entity tag is a properly formatted string. In ABNF:
249
+ Make sure your entity tag is a properly formatted string. In ABNF:
249
250
 
250
- entity-tag = [ "W/" ] quoted-string
251
- quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
252
- qdtext = <any TEXT except <">>
253
- quoted-pair = "\" CHAR
251
+ entity-tag = [ "W/" ] quoted-string
252
+ quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
253
+ qdtext = <any TEXT except <">>
254
+ quoted-pair = "\" CHAR
254
255
 
255
- @abstract
256
- @return [String]
257
- @see http://tools.ietf.org/html/rfc2616#section-14.19 RFC2616 section 14.19
258
- @since 0.0.1
256
+ @abstract
257
+ @return [String]
258
+ @see http://tools.ietf.org/html/rfc2616#section-14.19 RFC2616 section 14.19
259
259
  =end
260
260
 
261
261
 
262
262
  =begin markdown
263
263
  @!attribute [r] get_last_modified
264
- Last modification of this resource.
265
-
266
- If your classes implement this method, then a `Last-Modified:` response
267
- header is generated automatically when appropriate. This allows clients to
268
- perform conditional requests, by sending an `If-Modified-Since:` or
269
- `If-Unmodified-Since:` request header. These conditions are then asserted
270
- for you automatically.
271
- @abstract
272
- @return [Array<(Time, Boolean)>] The timestamp, and a flag indicating if the
273
- timestamp is a strong validator.
274
- @see http://tools.ietf.org/html/rfc2616#section-14.29 RFC2616 section 14.29
275
- @since 0.0.1
264
+ Last modification of this resource.
265
+
266
+ If your classes implement this method, then a `Last-Modified:` response
267
+ header is generated automatically when appropriate. This allows clients to
268
+ perform conditional requests, by sending an `If-Modified-Since:` or
269
+ `If-Unmodified-Since:` request header. These conditions are then asserted
270
+ for you automatically.
271
+ @abstract
272
+ @return [Array<(Time, Boolean)>] The timestamp, and a flag indicating if the
273
+ timestamp is a strong validator.
274
+ @see http://tools.ietf.org/html/rfc2616#section-14.29 RFC2616 section 14.29
276
275
  =end
277
276
 
278
277
 
279
278
  =begin markdown
280
279
  @!method destroy()
281
- @return [Hash, nil] an optional header hash.
280
+ @return [Hash, nil] an optional header hash.
282
281
  =end
283
282
 
284
283
 
@@ -287,7 +286,6 @@ List of all HTTP/1.1 methods implemented by this resource.
287
286
 
288
287
  This works by inspecting all the {#do_METHOD} methods this object implements.
289
288
  @return [Array<Symbol>]
290
- @since 0.0.1
291
289
  @private
292
290
  =end
293
291
  def http_methods
@@ -295,10 +293,10 @@ This works by inspecting all the {#do_METHOD} methods this object implements.
295
293
  if self.empty?
296
294
  self.class.all_media_types
297
295
  else
298
- r.merge! [ :OPTIONS, :HEAD, :GET ]
296
+ r.push( :OPTIONS, :HEAD, :GET )
299
297
  r << :DELETE if self.respond_to?( :destroy )
300
298
  end
301
- self.public_instance_methods.each do
299
+ self.class.public_instance_methods.each do
302
300
  |instance_method|
303
301
  if /\Ado_([A-Z])+\z/ === instance_method
304
302
  r << $1.to_sym
@@ -319,10 +317,9 @@ returned (without an entity body).
319
317
  Feel free to override this method at will.
320
318
  @return [void]
321
319
  @raise [HTTP404NotFound] `404 Not Found` if this resource is empty.
322
- @since 0.0.1
323
320
  =end
324
321
  def http_OPTIONS request, response
325
- raise HTTP404NotFound if self.empty?
322
+ raise HTTP404NotFound, path if self.empty?
326
323
  response.status = status_code :no_content
327
324
  response.header['Allow'] = self.http_methods.join ', '
328
325
  end
@@ -336,7 +333,6 @@ then strips off the response body.
336
333
 
337
334
  Feel free to override this method at will.
338
335
  @return [self]
339
- @since 0.0.1
340
336
  =end
341
337
  def http_HEAD request, response
342
338
  self.http_GET request, response
@@ -352,12 +348,13 @@ Feel free to override this method at will.
352
348
 
353
349
  =begin markdown
354
350
  @private
351
+ @param [Rackful::Request] request
352
+ @param [Rack::Response] response
355
353
  @return [void]
356
354
  @raise [HTTP404NotFound, HTTP405MethodNotAllowed]
357
- @since 0.0.1
358
355
  =end
359
356
  def http_GET request, response
360
- raise HTTP404NotFound if self.empty?
357
+ raise HTTP404NotFound, path if self.empty?
361
358
  # May throw HTTP406NotAcceptable:
362
359
  content_type = self.class.best_content_type( request.accept )
363
360
  response['Content-Type'] = content_type
@@ -377,12 +374,12 @@ Wrapper around {#do_METHOD #do_GET}
377
374
  @private
378
375
  @return [void]
379
376
  @raise [HTTP404NotFound, HTTP405MethodNotAllowed]
380
- @since 0.0.1
381
377
  =end
382
378
  def http_DELETE request, response
383
- raise HTTP404NotFound if self.empty?
379
+ raise HTTP404NotFound, path if self.empty?
380
+ raise HTTP405MethodNotAllowed unless self.respond_to?( :destroy )
384
381
  response.status = status_code( :no_content )
385
- if headers = self.destroy
382
+ if headers = self.destroy( request, response )
386
383
  response.headers.merge! headers
387
384
  end
388
385
  end
@@ -391,8 +388,7 @@ Wrapper around {#do_METHOD #do_GET}
391
388
  =begin markdown
392
389
  @private
393
390
  @return [void]
394
- @raise [HTTP415UnsupportedMediaType] `405 Method Not Allowed` if the resource doesn't implement the `PUT` method.
395
- @since 0.0.1
391
+ @raise [HTTP415UnsupportedMediaType, HTTP405MethodNotAllowed] if the resource doesn't implement the `PUT` method.
396
392
  =end
397
393
  def http_PUT request, response
398
394
  raise HTTP405MethodNotAllowed unless self.respond_to? :do_PUT
@@ -411,7 +407,6 @@ Wrapper around {#do_METHOD #do_PUT}
411
407
  @private
412
408
  @return [void]
413
409
  @raise [HTTPStatus] `405 Method Not Allowed` if the resource doesn't implement the `PUT` method.
414
- @since 0.0.1
415
410
  =end
416
411
  def http_method request, response
417
412
  method = request.request_method.to_sym
@@ -430,7 +425,6 @@ Wrapper around {#do_METHOD #do_PUT}
430
425
 
431
426
  =begin markdown
432
427
  Adds `ETag:` and `Last-Modified:` response headers.
433
- @since 0.0.1
434
428
  =end
435
429
  def default_headers
436
430
  r = {}
@@ -444,4 +438,34 @@ Adds `ETag:` and `Last-Modified:` response headers.
444
438
 
445
439
  end # module Resource
446
440
 
441
+
442
+ =begin unused
443
+ module Collection
444
+
445
+
446
+ include Enumerable
447
+
448
+
449
+ def self.included( modul )
450
+ unless modul.kind_of? Resource
451
+ raise "module #{self} included in #{modul}, which isn't a Rackful::Resource"
452
+ end
453
+ end
454
+
455
+
456
+ def recurse?; false; end
457
+
458
+
459
+ def each_pair
460
+ self.each do
461
+ |path|
462
+ yield [ path, Request.current.resource_factory( path ) ]
463
+ end
464
+ end
465
+
466
+
467
+ end # module Collection
468
+ =end
469
+
470
+
447
471
  end # module Rackful