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.
- data/History.txt +5 -0
- data/Manifest.txt +2 -4
- data/README.rdoc +1 -1
- data/lib/described_routes.rb +1 -1
- data/lib/described_routes/middleware/base.rb +218 -0
- data/lib/described_routes/middleware/rails.rb +50 -0
- metadata +4 -6
- data/test_rails_app/log/development.log +0 -0
- data/test_rails_app/log/production.log +0 -0
- data/test_rails_app/log/server.log +0 -0
- data/test_rails_app/log/test.log +0 -0
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -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
|
data/README.rdoc
CHANGED
@@ -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
|
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
|
|
data/lib/described_routes.rb
CHANGED
@@ -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.
|
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-
|
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
|
data/test_rails_app/log/test.log
DELETED
File without changes
|