described_routes 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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