described_routes 0.0.2 → 0.0.3

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ path-to, Copyright (c) 2009 Mike Burrows
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt CHANGED
@@ -1,12 +1,16 @@
1
1
  History.txt
2
+ LICENSE
2
3
  Manifest.txt
3
4
  PostInstall.txt
4
5
  README.rdoc
5
6
  Rakefile
6
7
  lib/described_routes.rb
8
+ lib/described_routes/rails_routes.rb
9
+ lib/described_routes/resource_template.rb
7
10
  lib/tasks/described_routes.rb
8
11
  script/console
9
12
  script/destroy
10
13
  script/generate
11
14
  test/test_described_routes.rb
12
15
  test/test_helper.rb
16
+ test/test_resource_template.rb
@@ -0,0 +1,153 @@
1
+ require 'described_routes'
2
+
3
+ module DescribedRoutes
4
+ module RailsRoutes
5
+ #
6
+ # Based on the implementation of "rake routes". Returns a hash of Rails path specifications (slightly normalized)
7
+ # mapped to hashes of the attributes we need.
8
+ #
9
+ def self.get_rails_resources
10
+ ActionController::Routing::Routes.routes.inject({}) do |resources, route|
11
+ name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s
12
+ controller = route.parameter_shell[:controller]
13
+ action = route.parameter_shell[:action]
14
+ options = [route.conditions[:method]].flatten.map{|option| option.to_s.upcase}
15
+ segs = route.segments.inject("") {|str,s| str << s.to_s }
16
+ segs.chop! if segs.length > 1
17
+
18
+ # prefix :id parameters consistently
19
+ # TODO - probably a better way to do this, just need a pattern that matches :id and not :id[a-zA-Z0-9_]+
20
+ segs.gsub!(/:[a-zA-Z0-9_]+/) do |match|
21
+ if match == ":id" && controller
22
+ ":#{controller.singularize.sub(/.*\//, "")}_id"
23
+ else
24
+ match
25
+ end
26
+ end
27
+
28
+ # ignore optional format parameter when comparing paths
29
+ key = segs.sub("(.:format)", "")
30
+ if resources[key]
31
+ # we've seen the (normalised) path before; add to options
32
+ resources[key]["options"] += options
33
+ else
34
+ template = segs
35
+
36
+ # collect & format mandatory parameters
37
+ params = []
38
+ template.gsub!(/:[a-zA-Z0-9_]+/) do |match|
39
+ param = match[1..-1]
40
+ param = controller.singularize.sub(/.*\//, "") + "_id" if param == "id" && controller
41
+ params << param
42
+ "{#{param}}"
43
+ end
44
+
45
+ # collect & format optional format parameter
46
+ optional_params = []
47
+ template.sub!("(.{format})") do |match|
48
+ optional_params << "format"
49
+ "{-prefix|.|format}"
50
+ end
51
+ params -= optional_params
52
+
53
+ # so now we have (for example):
54
+ # segs #=> "/users/:user_id/edit(.:format)" (was "/users/:id")
55
+ # key #=> "/users/:user_id/edit"
56
+ # template #=> "/users/{user_id}/edit"
57
+ # params #=> ["user_id"]
58
+ # optional_params #=> ["format"]
59
+ # action #=> "edit"
60
+ # options #=> ["GET"]
61
+ # name #=> "edit_user"
62
+
63
+ # create a new route hash
64
+ resource = {
65
+ "path_template" => template,
66
+ "options" => options,
67
+ }
68
+ resource["params"] = params unless params.empty?
69
+ resource["optional_params"] = optional_params unless optional_params.empty?
70
+
71
+ resources[key] = resource
72
+ end
73
+
74
+ # this may be the first time we've seen a good name for this key
75
+ resources[key]["name"] ||= name unless name.blank? or name =~ /^formatted/
76
+
77
+ resources
78
+ end
79
+ end
80
+
81
+ #
82
+ # Takes the routes from Rails and produces the required tree structure.
83
+ #
84
+ def self.get_resources
85
+ resources = get_rails_resources
86
+ resources.delete_if{|k, v| v["name"].blank? or v["name"] =~ /^formatted/}
87
+
88
+ key_tree = make_key_tree(resources.keys.sort){|possible_prefix, key|
89
+ key[0...possible_prefix.length] == possible_prefix && possible_prefix != "/"
90
+ }
91
+
92
+ tree = map_key_tree(key_tree) do |key, children|
93
+ resource = resources[key]
94
+ resource["resource_templates"] = children unless children.empty?
95
+ resource.delete("options") if resource["options"] == [""]
96
+
97
+ # compare parent and child names, and populate "rel" with either
98
+ # 1) a prefix (probably an action name)
99
+ # 2) a suffix (probably a nested resource)
100
+ # If neither applies, let's hope the child is identified by parameter (i.e. the parent is a collection)
101
+ # TODO rewrite this so that it's done when the child is created
102
+ name = resource["name"]
103
+ prefix = /^(.*)_#{name}$/
104
+ suffix = /^#{name}_(.*)$/
105
+ children.each do |child|
106
+ child_name = child["name"]
107
+ if child_name =~ prefix
108
+ child["rel"] = $1
109
+ elsif child_name =~ suffix
110
+ child["rel"] = $1
111
+ end
112
+ end
113
+
114
+ resource
115
+ end
116
+ end
117
+
118
+ #
119
+ # Depth-first tree traversal
120
+ #
121
+ # tree = [["/", []], ["/a", [["/a/b", [["/a/b/c", []]]], ["/a/d", []]]], ["/b", []]]
122
+ # map_key_tree(tree){|key, processed_children| {key => processed_children}}
123
+ # # => [{"/"=>[]}, {"/a"=>[{"/a/b"=>[{"/a/b/c"=>[]}]}, {"/a/d"=>[]}]}, {"/b"=>[]}]
124
+ #
125
+ def self.map_key_tree(tree, &blk) #:nodoc:
126
+ tree.map do |pair|
127
+ key, children = pair
128
+ blk.call(key, map_key_tree(children, &blk))
129
+ end
130
+ end
131
+
132
+ #
133
+ # Turns a sorted array of strings into a tree structure as follows:
134
+ #
135
+ # make_key_tree(["/", "/a", "/a/b", "/a/b/c", "/a/d", "/b"]){|possible_prefix, route|
136
+ # route[0...possible_prefix.length] == possible_prefix && possible_prefix != "/"
137
+ # }
138
+ # => [["/", []], ["/a", [["/a/b", [["/a/b/c", []]]], ["/a/d", []]]], ["/b", []]]
139
+ #
140
+ # Note that in the example (as in is actual usage in this module), we choose not to to have the root resource ("/") as
141
+ # the parent of all other resources.
142
+ #
143
+ def self.make_key_tree(sorted_keys, &is_prefix) #:nodoc:
144
+ head, *tail = sorted_keys
145
+ if head
146
+ children, siblings = tail.partition{|p| is_prefix.call(head, p)}
147
+ [[head, make_key_tree(children, &is_prefix)]] + make_key_tree(siblings, &is_prefix)
148
+ else
149
+ []
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,66 @@
1
+ require "json"
2
+
3
+ module DescribedRoutes
4
+ class ResourceTemplate
5
+ # The template's name. Optional. Making these unique across the application is helpful for clients
6
+ # that may wish to pick out nested templates by name.
7
+ attr_reader :name
8
+
9
+ # Optional attribute that describes a resource's relationship to its parent. For example:
10
+ # * a nested route to a resource's edit page would have rel of "edit"
11
+ # * a nested collection of articles under a "user" resource would have have a rel of "articles"
12
+ # Collection members generally don't need a rel as they are identified by their params
13
+ attr_reader :rel
14
+
15
+ # A template for generating paths relative to the application's base.
16
+ attr_reader :path_template
17
+
18
+ # The parameters required by the path template
19
+ attr_reader :params
20
+
21
+ # Optional paramaters that may be used by the path template
22
+ attr_reader :optional_params
23
+
24
+ # "options" in the sense of the HTTP option request - i.e. a list of HTTP methods. Optional.
25
+ attr_reader :options
26
+
27
+ # An optional list of nested resource templates
28
+ attr_reader :resource_templates
29
+
30
+ # Initialize a ResourceTemplate. See the attribute descriptions above for explanations of the parameters.
31
+ def initialize(name, rel, path_template, params, optional_params, options, resource_templates)
32
+ @name, @rel, @path_template = name, rel, path_template
33
+ @params = params || []
34
+ @optional_params = optional_params || []
35
+ @options = options || []
36
+ @resource_templates = resource_templates || []
37
+ end
38
+
39
+ # Create a ResourceTemplate from its Hash representation
40
+ def self.from_hash(hash)
41
+ attributes = %w(name rel path_template params optional_params options).map{|k| hash[k]}
42
+ if hash["resource_templates"]
43
+ attributes << hash["resource_templates"].map{|h| from_hash(h)} if hash["resource_templates"]
44
+ else
45
+ attributes << nil
46
+ end
47
+ self.new(*attributes)
48
+ end
49
+
50
+ # Convert to a hash (equivalent to its JSON or YAML representation)
51
+ def to_hash
52
+ hash = {}
53
+ hash["name"] = name if name && !name.empty?
54
+ hash["rel"] = rel if rel && !rel.empty?
55
+ hash["path_template"] = path_template if path_template && !path_template.empty?
56
+
57
+ hash["options"] = options if options && !options.empty?
58
+
59
+ hashes = DescribedRoutes.to_parsed(resource_templates)
60
+ hash["resource_templates"] = hashes if hashes && !hashes.empty?
61
+
62
+ hash
63
+ end
64
+ end
65
+ end
66
+
@@ -2,7 +2,7 @@ require 'described_routes/resource_template'
2
2
 
3
3
  module DescribedRoutes
4
4
  # rubygem version
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.3"
6
6
 
7
7
  # Convert an array of ResourceTemplate objects to array of hashes equivalent to their JSON or YAML representations
8
8
  def self.to_parsed(resource_templates)
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.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Burrows
@@ -46,17 +46,21 @@ extra_rdoc_files:
46
46
  - README.rdoc
47
47
  files:
48
48
  - History.txt
49
+ - LICENSE
49
50
  - Manifest.txt
50
51
  - PostInstall.txt
51
52
  - README.rdoc
52
53
  - Rakefile
53
54
  - lib/described_routes.rb
55
+ - lib/described_routes/rails_routes.rb
56
+ - lib/described_routes/resource_template.rb
54
57
  - lib/tasks/described_routes.rb
55
58
  - script/console
56
59
  - script/destroy
57
60
  - script/generate
58
61
  - test/test_described_routes.rb
59
62
  - test/test_helper.rb
63
+ - test/test_resource_template.rb
60
64
  has_rdoc: true
61
65
  homepage:
62
66
  licenses: []