diff options
author | Andrew White <andyw@pixeltrix.co.uk> | 2012-12-19 20:54:47 +0000 |
---|---|---|
committer | Andrew White <andyw@pixeltrix.co.uk> | 2012-12-19 22:13:08 +0000 |
commit | 56fee39c392788314c44a575b3fd66e16a50c8b5 (patch) | |
tree | e12603fff0d1e7c69d021f822b4077a74b91ddc4 /actionpack/lib/action_dispatch/journey/formatter.rb | |
parent | b225693a0d86f2e33c66049a69e5148e5c93b7cd (diff) | |
download | rails-56fee39c392788314c44a575b3fd66e16a50c8b5.tar.gz rails-56fee39c392788314c44a575b3fd66e16a50c8b5.tar.bz2 rails-56fee39c392788314c44a575b3fd66e16a50c8b5.zip |
Integrate Journey into Action Dispatch
Move the Journey code underneath the ActionDispatch namespace so
that we don't pollute the global namespace with names that may
be used for models.
Fixes rails/journey#49.
Diffstat (limited to 'actionpack/lib/action_dispatch/journey/formatter.rb')
-rw-r--r-- | actionpack/lib/action_dispatch/journey/formatter.rb | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb new file mode 100644 index 0000000000..873a73baf0 --- /dev/null +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -0,0 +1,147 @@ +module ActionDispatch + module Journey + ### + # The Formatter class is used for formatting URLs. For example, parameters + # passed to +url_for+ in rails will eventually call Formatter#generate + class Formatter + attr_reader :routes + + def initialize routes + @routes = routes + @cache = nil + end + + def generate type, name, options, recall = {}, parameterize = nil + constraints = recall.merge options + missing_keys = [] + + match_route(name, constraints) do |route| + parameterized_parts = extract_parameterized_parts route, options, recall, parameterize + next if !name && route.requirements.empty? && route.parts.empty? + + missing_keys = missing_keys(route, parameterized_parts) + next unless missing_keys.empty? + params = options.dup.delete_if do |key, _| + parameterized_parts.key?(key) || route.defaults.key?(key) + end + + return [route.format(parameterized_parts), params] + end + + raise Router::RoutingError.new "missing required keys: #{missing_keys}" + end + + def clear + @cache = nil + end + + private + def extract_parameterized_parts route, options, recall, parameterize = nil + constraints = recall.merge options + data = constraints.dup + + keys_to_keep = route.parts.reverse.drop_while { |part| + !options.key?(part) || (options[part] || recall[part]).nil? + } | route.required_parts + + (data.keys - keys_to_keep).each do |bad_key| + data.delete bad_key + end + + parameterized_parts = data.dup + + if parameterize + parameterized_parts.each do |k,v| + parameterized_parts[k] = parameterize.call(k, v) + end + end + + parameterized_parts.keep_if { |_,v| v } + parameterized_parts + end + + def named_routes + routes.named_routes + end + + def match_route name, options + if named_routes.key? name + yield named_routes[name] + else + #routes = possibles(@cache, options.to_a) + routes = non_recursive(cache, options.to_a) + + hash = routes.group_by { |_, r| + r.score options + } + + hash.keys.sort.reverse_each do |score| + next if score < 0 + + hash[score].sort_by { |i,_| i }.each do |_,route| + yield route + end + end + end + end + + def non_recursive cache, options + routes = [] + stack = [cache] + + while stack.any? + c = stack.shift + routes.concat c[:___routes] if c.key? :___routes + + options.each do |pair| + stack << c[pair] if c.key? pair + end + end + + routes + end + + # returns an array populated with missing keys if any are present + def missing_keys route, parts + missing_keys = [] + tests = route.path.requirements + route.required_parts.each { |key| + if tests.key? key + missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key] + else + missing_keys << key unless parts[key] + end + } + missing_keys + end + + def possibles cache, options, depth = 0 + cache.fetch(:___routes) { [] } + options.find_all { |pair| + cache.key? pair + }.map { |pair| + possibles(cache[pair], options, depth + 1) + }.flatten(1) + end + + # returns boolean, true if no missing keys are present + def verify_required_parts! route, parts + missing_keys(route, parts).empty? + end + + def build_cache + root = { :___routes => [] } + routes.each_with_index do |route, i| + leaf = route.required_defaults.inject(root) do |h, tuple| + h[tuple] ||= {} + end + (leaf[:___routes] ||= []) << [i, route] + end + root + end + + def cache + @cache ||= build_cache + end + end + end +end |