Module: DescribedRoutes::RailsRoutes

Defined in:
lib/described_routes/rails_routes.rb

Defined Under Namespace

Classes: RailsResourceTemplates

Class Method Summary collapse

Class Method Details

.get_parsed_rails_resources(base_url = nil) ⇒ Object

Takes the routes from Rails and produces the required tree structure. Returns the “parsed” format - i.e. a representation in Ruby Array and Hash objects



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/described_routes/rails_routes.rb', line 137

def self.get_parsed_rails_resources(base_url = nil) #:nodoc:
  base_url = base_url.sub(/\/$/, '') if base_url
  resources = get_rails_resources
  resources.delete_if{|k, v| v["name"].blank? or v["name"] =~ /^formatted/}

  key_tree = make_key_tree(resources.keys.sort){|possible_prefix, key|
    key[0...possible_prefix.length] == possible_prefix && possible_prefix != "/"
  }

  tree = map_key_tree(key_tree) do |key, children|
    resource = resources[key]
    
    resource.delete("options") if resource["options"] == [""]
    resource["uri_template"] = base_url + resource["path_template"] if base_url && resource["path_template"]

    # compare parent and child names, and populate "rel" with either
    # 1) a prefix (probably an action name)
    # 2) a suffix (probably a nested resource)
    # 3) the child's name if the parent and child's params are identical
    # If none of the above applies, the child must be identifable by parameter
    name = resource["name"]
    prefix = /^(.*)_#{name}$/
    suffix = /^#{name}_(.*)$/
    children.each do |child|
      child_name = child["name"]
      if child_name =~ prefix
        child["rel"] = $1
      elsif child_name =~ suffix
        child["rel"] = $1
      elsif child["params"] == resource["params"]
        child["rel"] = child["name"]
      end
    end

    controller = resource["controller"]
    unless children.empty?
      resource["resource_templates"] = children.sort_by{|c|
        [
          (c["controller"] == controller) ? "" : c["controller"],  # group by controller, parent controller first
          (c["params"] || []).length,                              # fewer params first
          c["name"]                                                # make determininistic 
        ]
      }
    end

    resource
  end
end

.get_rails_resourcesObject

Based on the implementation of “rake routes”. Returns a hash of Rails path specifications (slightly normalized) mapped to hashes of the attributes we need.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/described_routes/rails_routes.rb', line 54

def self.get_rails_resources #:nodoc:
  ActionController::Routing::Routes.routes.inject({}) do |resources, route|
    name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s
    controller = route.parameter_shell[:controller]
    action = route.parameter_shell[:action]
    options = [route.conditions[:method]].flatten.map{|option| option.to_s.upcase}
    segs = route.segments.inject("") {|str,s| str << s.to_s }
    segs.chop! if segs.length > 1

    # prefix :id parameters consistently
    # TODO - probably a better way to do this, just need a pattern that matches :id and not :id[a-zA-Z0-9_]+
    id_name = nil
    segs.gsub!(/:[a-zA-Z0-9_]+/) do |match|
      if match == ":id" && controller
        id_name = (controller == "described_routes/rails") ? "route_name" : "#{controller.singularize.sub(/.*\//, "")}_id"
        ':' + id_name
      else
        match
      end
    end

    # ignore optional format parameter when comparing paths
    key = segs.sub("(.:format)", "")
    if resources[key]
      # we've seen the (normalised) path before; add to options
      resources[key]["options"] += options
    else
      template = segs

      # collect & format mandatory parameters
      params = []
      template.gsub!(/:[a-zA-Z0-9_]+/) do |match|
        param = match[1..-1]
        param = controller.singularize.sub(/.*\//, "") + "_id" if param == "id" && controller
        params << param
        "{#{param}}"
      end

      # collect & format optional format parameter
      optional_params = []
      template.sub!("(.{format})") do |match|
        optional_params << "format"
        "{-prefix|.|format}"
      end
      params -= optional_params

      # so now we have (for example):
      #   segs              #=> "/users/:user_id/edit(.:format)" (was "/users/:id")
      #   key               #=> "/users/:user_id/edit"
      #   template          #=> "/users/{user_id}/edit"
      #   params            #=> ["user_id"]
      #   optional_params   #=> ["format"]
      #   action            #=> "edit"
      #   options           #=> ["GET"]
      #   name              #=> "edit_user"
      #   controller        #=> "rails"
      #   id_name           #=> "user_id"

      # create a new route hash
      resource = {
        "path_template" => template,
        "options"       => options,
        "controller"    => controller,
        "action"        => action,
        "id_name"       => id_name
      }
      resource["params"] = params unless params.empty?
      resource["optional_params"] = optional_params unless optional_params.empty?

      resources[key] = resource
    end

    # this may be the first time we've seen a good name for this key
    resources[key]["name"] ||= name unless name.blank? or name =~ /^formatted/

    resources
  end
end

.get_resource_templates(base_url = nil, routing = nil) ⇒ Object

Process Rails routes and return an array of ResourceTemplate objects.



44
45
46
47
48
# File 'lib/described_routes/rails_routes.rb', line 44

def self.get_resource_templates(base_url=nil, routing=nil)
  parsed = get_parsed_rails_resources(base_url)
  parsed = parsed_hook.call(parsed) if parsed_hook
  RailsResourceTemplates.new(parsed)
end

.make_key_tree(sorted_keys, &is_prefix) ⇒ Object

Turns a sorted array of strings into a tree structure as follows:

make_key_tree(["/", "/a", "/a/b", "/a/b/c", "/a/d", "/b"]){|possible_prefix, route|
  route[0...possible_prefix.length] == possible_prefix && possible_prefix != "/"
}
=> [["/", []], ["/a", [["/a/b", [["/a/b/c", []]]], ["/a/d", []]]], ["/b", []]]

Note that in the example (as in is actual usage in this module), we choose not to to have the root resource (“/”) as the parent of all other resources.



211
212
213
214
215
216
217
218
219
# File 'lib/described_routes/rails_routes.rb', line 211

def self.make_key_tree(sorted_keys, &is_prefix) #:nodoc:
  head, *tail = sorted_keys
  if head
    children, siblings = tail.partition{|p| is_prefix.call(head, p)}
    [[head, make_key_tree(children, &is_prefix)]] + make_key_tree(siblings, &is_prefix)
  else
    []
  end
end

.map_key_tree(tree, &blk) ⇒ Object

Depth-first tree traversal

tree = [["/", []], ["/a", [["/a/b", [["/a/b/c", []]]], ["/a/d", []]]], ["/b", []]]
map_key_tree(tree){|key, processed_children| {key => processed_children}}
# => [{"/"=>[]}, {"/a"=>[{"/a/b"=>[{"/a/b/c"=>[]}]}, {"/a/d"=>[]}]}, {"/b"=>[]}]


193
194
195
196
197
198
# File 'lib/described_routes/rails_routes.rb', line 193

def self.map_key_tree(tree, &blk) #:nodoc:
  tree.map do |pair|
    key, children = pair
    blk.call(key, map_key_tree(children, &blk))
  end
end