described_routes 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,8 @@
1
+ == 0.8.1 2009-08-03
2
+
3
+ * Link headers on the described_routes ResourceTemplate(s) metadata served by the middleware
4
+ * Fix gem manifest
5
+
1
6
  == 0.8.0 2009-08-01
2
7
 
3
8
  * Rails integration via Rack middleware!
@@ -6,6 +6,8 @@ README.rdoc
6
6
  Rakefile
7
7
  lib/described_routes.rb
8
8
  lib/described_routes/helpers/described_routes_helper.rb
9
+ lib/described_routes/middleware/base.rb
10
+ lib/described_routes/middleware/rails.rb
9
11
  lib/described_routes/rails_controller.rb
10
12
  lib/described_routes/rails_routes.rb
11
13
  lib/described_routes/rake_task_methods.rb
@@ -33,10 +35,6 @@ test_rails_app/config/initializers/described_routes.rb
33
35
  test_rails_app/config/initializers/new_rails_defaults.rb
34
36
  test_rails_app/config/initializers/session_store.rb
35
37
  test_rails_app/config/routes.rb
36
- test_rails_app/log/development.log
37
- test_rails_app/log/production.log
38
- test_rails_app/log/server.log
39
- test_rails_app/log/test.log
40
38
  test_rails_app/script/server
41
39
  test_rails_app/test/fixtures/build_time/described_routes.json
42
40
  test_rails_app/test/fixtures/build_time/described_routes.text
@@ -176,7 +176,7 @@ JSON example (after pretty printing):
176
176
 
177
177
  A discovery protocol based on link headers is added automatically by the middleware (controller changes are no longer required). This protocol is understood by <code>path-to</code> (enabling client APIs to be bootstrapped easily) and the link headers can be regarded as adding useful type information to resources.
178
178
 
179
- Regular resources are given a link header that points to that resource's <code>ResourceTemplate</code> metadata. That in turn is given a link header that points to the </code>ResourceTemplates</code> metadata for the entire application. The root resource has a link header that points to the </code>ResourceTemplates</code> metadata directly.
179
+ Regular resources are given a link header that points to that resource's <code>ResourceTemplate</code> metadata. That in turn is given a link header that points to the <code>ResourceTemplates</code> metadata for the entire application. The root resource has a link header that points to the <code>ResourceTemplates</code> metadata directly.
180
180
 
181
181
  For further information on link headers, see the draft spec http://tools.ietf.org/id/draft-nottingham-http-link-header-06.txt and the <code>link_header</code> gem.
182
182
 
@@ -2,5 +2,5 @@ require 'resource_template'
2
2
 
3
3
  module DescribedRoutes
4
4
  # rubygem version
5
- VERSION = "0.8.0"
5
+ VERSION = "0.8.1"
6
6
  end
@@ -0,0 +1,218 @@
1
+ require 'rack'
2
+ require 'rack/request'
3
+ require 'rack/respond_to'
4
+ require 'link_header'
5
+
6
+ module DescribedRoutes
7
+ module Middleware
8
+ #
9
+ # Abstract Rack middleware for described_routes. It serves ResourceTemplate data at the configured descrbed_routes path and
10
+ # adds link headers to regular requests whose routing matches a ResourceTemplate.
11
+ #
12
+ # It must be customised to the web framework in use - override #get_resource_templates and #get_resource_routing.
13
+ #
14
+ class Base
15
+ include Rack::RespondTo
16
+
17
+ # The default options parameter to #link_headers; controls which links appear in html link elements
18
+ DEFAULT_OPTIONS = {
19
+ :describedby => true,
20
+ :self => false,
21
+ :up => false,
22
+ :related => false,
23
+ :registered_rels => {'edit' => 'edit', 'up' => 'up'},
24
+ :described_routes_path => '/described_routes'
25
+ }
26
+
27
+ def initialize(app, options={})
28
+ @app = app
29
+
30
+ @options = DEFAULT_OPTIONS.merge(options)
31
+ DEFAULT_OPTIONS.keys.each do |option|
32
+ instance_variable_set("@option_#{option}", @options[option])
33
+ end
34
+
35
+ @inited = false
36
+ end
37
+
38
+ #
39
+ # From the first request, initialize @root, @described_routes_uri and @resource_templates
40
+ #
41
+ def init_from_first_req(req)
42
+ raise "no request host" unless req.host
43
+
44
+ @root = "http://#{req.host}"
45
+ @root += ":#{req.port}" if req.port && req.port != 80
46
+ @root += "#{script_name}" if req.script_name && !req.script_name.empty?
47
+
48
+ @described_routes_uri = @root + @option_described_routes_path
49
+
50
+ @resource_templates = get_resource_templates(@root)
51
+ raise "get_resource_templates(#{@root.inspect}) failed; no resource templates!" unless @resource_templates
52
+
53
+ @inited = true
54
+ end
55
+
56
+ #
57
+ # Does nothing - override in framework-specific middleware to return the top level ResourceTemplates object
58
+ #
59
+ def get_resource_templates
60
+ nil
61
+ end
62
+
63
+ #
64
+ # Does nothing - override in framwork-specific middleware to return the ResourceTemplate and params hash matching the
65
+ # request, otherwise a pair of nils
66
+ #
67
+ def get_resource_routing(req)
68
+ [nil, nil]
69
+ end
70
+
71
+ #
72
+ # Process a Rack request, either returning ResourceTemplate data fif the request matches the described_routes path,
73
+ # otherwise passing on the request the application and adding a link header to the response.
74
+ #
75
+ def call(env)
76
+ # puts "\n", "-" * 80, "\n", env.map{|k, v| "#{k} => #{v.inspect}"}
77
+
78
+ req = Rack::Request.new(env)
79
+
80
+ init_from_first_req(req) unless @inited
81
+
82
+ if req.path =~ %r(^#{@option_described_routes_path}(/([^/.]+)?)?(\.([a-z]+))?)
83
+ serve_resource_template_data(req, $2, $3)
84
+ else
85
+ call_with_link_header(req)
86
+ end
87
+ end
88
+
89
+ #
90
+ # Handles requests for ResourceTemplate data
91
+ #
92
+ def serve_resource_template_data(req, route_name, format)
93
+ if route_name # /described_routes/{route_name}
94
+ resource_template = @resource_templates.all_by_name[route_name]
95
+ unless resource_template
96
+ return [404, {'Content-Type' => 'text/plain'}, ["No ResourceTemplate named #{route_name.inspect}"]]
97
+ end
98
+ target = resource_template
99
+ rel = "index" # link header will point to the site ResourceTemplates description
100
+ else
101
+ rel = "self" # this is the site ResourceTemplates description
102
+ target = @resource_templates
103
+ end
104
+ expanded = target.partial_expand(req.GET)
105
+ Rack::RespondTo.env = req.env
106
+ if format
107
+ # Format extension overrides any accept header
108
+ Rack::RespondTo.media_types = [Rack::Mime::MIME_TYPES[format]]
109
+ else
110
+ # Supported formats, .text preferred. No html yet!
111
+ Rack::RespondTo.media_types = %w(.text .json .yaml .xml).map{|format| Rack::Mime::MIME_TYPES[format]}
112
+ end
113
+
114
+ body = respond_to do |format|
115
+ format.text {expanded.to_text}
116
+ format.json {expanded.to_json}
117
+ format.yaml {expanded.to_yaml}
118
+ format.xml {expanded.to_xml(Builder::XmlMarkup.new(:indent => 2)).target!}
119
+ end
120
+
121
+ headers = {
122
+ 'Link' => %Q(<#{@described_routes_uri}>; rel="#{rel}"; meta="ResourceTemplates"),
123
+ 'Content-Type' => Rack::RespondTo.selected_media_type
124
+ }
125
+
126
+ [200, headers, [body]]
127
+ end
128
+
129
+ #
130
+ # Passes on a request to the application and adds a link header to the response
131
+ #
132
+ def call_with_link_header(req)
133
+ status, headers, body = @app.call(req.env)
134
+
135
+ resource_template, params = get_resource_routing(req)
136
+ if resource_template
137
+ headers = headers.merge("Link" => make_link_header(resource_template, params, @root + req.fullpath).to_s)
138
+ end
139
+
140
+ [status, headers, body]
141
+ end
142
+
143
+ # Returns a LinkHeader object that represents the required links.
144
+ #
145
+ # Link relation types ("rel" attributes) will contain a standard type ('self', 'up', 'describedby') &/or an extension type
146
+ # in the form "described_route_url(name)#rel", using the name and rel of the resource template.
147
+ #
148
+ # The output is filtered by the options hash, with members :self, :describedby, :up, :related.
149
+ #
150
+ # TODO move this to ResourceTemplate
151
+ #
152
+ def make_link_header(resource_template, params, request_uri)
153
+ links = []
154
+
155
+ type_prefix = @described_routes_uri + '#'
156
+ #
157
+ # For the application's root, the link with rel="describedby" has meta="ResourceTemplates" and it refers to a list of all
158
+ # top level resource templates. Otherwise, rel="describedby" has meta="ResourceTemplate" and it refers to a single resource
159
+ # template (together with any descendants).
160
+ #
161
+ if resource_template.name == 'root'
162
+ described_by = @described_routes_uri
163
+ related = @resource_templates
164
+ meta = "ResourceTemplates"
165
+ else
166
+ described_by = @described_routes_uri + "/" + resource_template.name
167
+ related = resource_template.resource_templates
168
+ meta = "ResourceTemplate"
169
+ end
170
+
171
+ #
172
+ # Add any query parameters to the rel="describedby" link
173
+ #
174
+ if params.empty?
175
+ described_by_with_params = described_by
176
+ else
177
+ described_by_with_params = described_by + '?' + params.to_query
178
+ end
179
+
180
+ # data for rel="self"
181
+ links << LinkHeader::Link.new(request_uri, [['rel', 'self'], ['role', type_prefix + resource_template.name]]) if @option_self
182
+
183
+ # data for rel="described_by"
184
+ links << LinkHeader::Link.new(described_by_with_params, [['rel', 'describedby'], ['meta', meta]]) if @option_describedby
185
+
186
+ # data for rel="up"
187
+ # TODO move this to ResourceTemplate
188
+ if @option_up
189
+ if resource_template.parent
190
+ links << LinkHeader::Link.new(
191
+ resource_template.parent.uri_for(params),
192
+ [['rel', 'up'], ['role', type_prefix + resource_template.parent.name]])
193
+ elsif resource_template.name != 'root'
194
+ links << LinkHeader::Link.new(@root + '/', [['rel', 'up'], ['role', type_prefix + 'root']])
195
+ end
196
+ end
197
+
198
+ # data for rel="related"
199
+ if @option_related
200
+ related.expand_links(params).each do |l|
201
+ if l.name != resource_template.name
202
+ rel = l.rel || l.name
203
+ rels = [['rel', described_by + '#' + rel]]
204
+ if l.rel
205
+ registered_rel = @option_registered_rels[rel]
206
+ if registered_rel
207
+ rels.unshift(['rel', registered_rel])
208
+ end
209
+ end
210
+ links << LinkHeader::Link.new(l.uri, rels + [['role', type_prefix + l.name]])
211
+ end
212
+ end
213
+ end
214
+ LinkHeader.new(links)
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,50 @@
1
+ require 'described_routes/middleware/base'
2
+ require 'described_routes/rails_routes'
3
+
4
+ module DescribedRoutes
5
+ module Middleware
6
+ #
7
+ # Rack middleware that integrates described_routes with Rails.
8
+ #
9
+ # In your environment.rb, add
10
+ # require "described_routes/middleware/rails"
11
+ # and include
12
+ # config.middleware.use DescribedRoutes::Middleware::Rails
13
+ # inside your <code>Rails::Initializer.run do...end block</code>
14
+ #
15
+ # Your Rails application will then serve ResourceTemplate data at the configured descrbed_routes path (/described_routes by
16
+ # default and adds link headers to regular requests whose routing matches a ResourceTemplate.
17
+ #
18
+ class Rails < Base
19
+ #
20
+ # Get a ResourceTemplates object from Rails. Override to suppress sensitive routes.
21
+ #
22
+ def get_resource_templates(root)
23
+ RailsRoutes.get_resource_templates(root)
24
+ end
25
+
26
+ #
27
+ # Returns a ResourceTemplate matching the request and its parameter hash, otherwise a pair of nils. The parameter hash
28
+ # is normalized by removing 'controller' and 'action' members and renaming 'id' (when present) to something resource-specific.
29
+ #
30
+ def get_resource_routing(req)
31
+ path_parameters = req.env["action_controller.request.path_parameters"]
32
+ query_parameters = req.env["action_controller.request.query_parameters"]
33
+
34
+ # Guess the route from the controller and action
35
+ controller_name, action_name = path_parameters["controller"], path_parameters["action"]
36
+ resource_template, id_name = @resource_templates.routing[[controller_name, action_name]]
37
+
38
+ if resource_template
39
+ params = path_parameters.merge(query_parameters).except("action", "controller")
40
+ if id_name && params[:id]
41
+ params[id_name] = params.delete(:id)
42
+ end
43
+ [resource_template, params]
44
+ else
45
+ [nil, nil]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: described_routes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Burrows
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-02 00:00:00 +01:00
12
+ date: 2009-08-03 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -79,6 +79,8 @@ files:
79
79
  - Rakefile
80
80
  - lib/described_routes.rb
81
81
  - lib/described_routes/helpers/described_routes_helper.rb
82
+ - lib/described_routes/middleware/base.rb
83
+ - lib/described_routes/middleware/rails.rb
82
84
  - lib/described_routes/rails_controller.rb
83
85
  - lib/described_routes/rails_routes.rb
84
86
  - lib/described_routes/rake_task_methods.rb
@@ -106,10 +108,6 @@ files:
106
108
  - test_rails_app/config/initializers/new_rails_defaults.rb
107
109
  - test_rails_app/config/initializers/session_store.rb
108
110
  - test_rails_app/config/routes.rb
109
- - test_rails_app/log/development.log
110
- - test_rails_app/log/production.log
111
- - test_rails_app/log/server.log
112
- - test_rails_app/log/test.log
113
111
  - test_rails_app/script/server
114
112
  - test_rails_app/test/fixtures/build_time/described_routes.json
115
113
  - test_rails_app/test/fixtures/build_time/described_routes.text
File without changes
File without changes
File without changes
File without changes