Class: ActionController::Routing::Route

Inherits:
Object
  • Object
show all
Defined in:
lib/action_controller/routing.rb

Overview

:nodoc:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, hash = {}) ⇒ Route

Returns a new instance of Route.

Raises:

  • (ArgumentError)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/action_controller/routing.rb', line 9

def initialize(path, hash={})
  raise ArgumentError, "Second argument must be a hash!" unless hash.kind_of?(Hash)
  @defaults = hash[:defaults].kind_of?(Hash) ? hash.delete(:defaults) : {}
  @requirements = hash[:requirements].kind_of?(Hash) ? hash.delete(:requirements) : {}
  self.items = path
  hash.each do |k, v|
    raise TypeError, "Hash keys must be symbols!" unless k.kind_of? Symbol
    if v.kind_of? Regexp
      raise ArgumentError, "Regexp requirement on #{k}, but #{k} is not in this route's path!" unless @items.include? k
      @requirements[k] = v
    else
      (@items.include?(k) ? @defaults : @requirements)[k] = v
    end
  end
  
  @defaults.each do |k, v|
    raise ArgumentError, "A default has been specified for #{k}, but #{k} is not in the path!" unless @items.include? k
    @defaults[k] = v.to_s unless v.kind_of?(String) || v.nil?
  end
  @requirements.each {|k, v| raise ArgumentError, "A Regexp requirement has been specified for #{k}, but #{k} is not in the path!" if v.kind_of?(Regexp) && ! @items.include?(k)}
  
  # Add in defaults for :action and :id.
  [[:action, 'index'], [:id, nil]].each do |name, default|
    @defaults[name] = default if @items.include?(name) && ! (@requirements.key?(name) || @defaults.key?(name))
  end
end

Instance Attribute Details

#defaultsObject (readonly)

The defaults hash



7
8
9
# File 'lib/action_controller/routing.rb', line 7

def defaults
  @defaults
end

Instance Method Details

#generate(options, defaults = {}) ⇒ Object

Generate a URL given the provided options. All values in options should be symbols. Returns the path and the unused names in a 2 element array. If generation fails, [nil, nil] is returned Generation can fail because of a missing value, or because an equality check fails.

Generate urls will be as short as possible. If the last component of a url is equal to the default value, then that component is removed. This is applied as many times as possible. So, your index controller’s index action will generate []



45
46
47
48
49
50
51
52
53
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
# File 'lib/action_controller/routing.rb', line 45

def generate(options, defaults={})
  non_matching = @requirements.keys.select {|name| ! passes_requirements?(name, options[name] || defaults[name])}
  non_matching.collect! {|name| requirements_for(name)}
  return nil, "Mismatching option#{'s' if non_matching.length > 1}:\n   #{non_matching.join '\n   '}" unless non_matching.empty?
  
  used_names = @requirements.inject({}) {|hash, (k, v)| hash[k] = true; hash} # Mark requirements as used so they don't get put in the query params
  components = @items.collect do |item|
    if item.kind_of? Symbol
      used_names[item] = true
      value = options[item] || defaults[item] || @defaults[item]
      return nil, requirements_for(item) unless passes_requirements?(item, value)
      defaults = {} unless defaults == {} || value == defaults[item] # Stop using defaults if this component isn't the same as the default.
      (value.nil? || item == :controller) ? value : CGI.escape(value.to_s)
    else item
    end
  end
  
  @items.reverse_each do |item| # Remove default components from the end of the generated url.
    break unless item.kind_of?(Symbol) && @defaults[item] == components.last
    components.pop
  end
  
  # If we have any nil components then we can't proceed.
  # This might need to be changed. In some cases we may be able to return all componets after nil as extras.
  missing = []; components.each_with_index {|c, i| missing << @items[i] if c.nil?}
  return nil, "No values provided for component#{'s' if missing.length > 1} #{missing.join ', '} but values are required due to use of later components" unless missing.empty? # how wide is your screen?
  
  unused = (options.keys - used_names.keys).inject({}) do |unused, key|
    unused[key] = options[key] if options[key] != @defaults[key]
    unused
  end
  
  components.collect! {|c| c.to_s}
  return components, unused
end

#inspectObject



125
126
127
128
129
# File 'lib/action_controller/routing.rb', line 125

def inspect
  when_str = @requirements.empty? ? "" : " when #{@requirements.inspect}"
  default_str = @defaults.empty? ? "" : " || #{@defaults.inspect}"
  "<#{self.class.to_s} #{@items.collect{|c| c.kind_of?(String) ? c : c.inspect}.join('/').inspect}#{default_str}#{when_str}>"
end

#recognize(components, options = {}) ⇒ Object

Recognize the provided path, returning a hash of recognized values, or [nil, reason] if the path isn’t recognized. The path should be a list of component strings. Options is a hash of the ?k=v pairs



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
# File 'lib/action_controller/routing.rb', line 84

def recognize(components, options={})
  options = options.clone
  components = components.clone
  controller_class = nil
  
  @items.each do |item|
    if item == :controller # Special case for controller
      if components.empty? && @defaults[:controller]
        controller_class, leftover = eat_path_to_controller(@defaults[:controller].split('/'))
        raise RoutingError, "Default controller does not exist: #{@defaults[:controller]}" if controller_class.nil? || leftover.empty? == false
      else
        controller_class, remaining_components = eat_path_to_controller(components)
        return nil, "No controller found at subpath #{components.join('/')}" if controller_class.nil?
        components = remaining_components
      end
      options[:controller] = controller_class.controller_path
      return nil, requirements_for(:controller) unless passes_requirements?(:controller, options[:controller])
    elsif item.kind_of? Symbol
      value = components.shift || @defaults[item]
      return nil, requirements_for(item) unless passes_requirements?(item, value)
      options[item] = value.nil? ? value : CGI.unescape(value)
    else
      return nil, "No value available for component #{item.inspect}" if components.empty?
      component = components.shift
      return nil, "Value for component #{item.inspect} doesn't match #{component}" if component != item
    end
  end
  
  if controller_class.nil? && @requirements[:controller] # Load a default controller
    controller_class, extras = eat_path_to_controller(@requirements[:controller].split('/'))
    raise RoutingError, "Illegal controller path for route default: #{@requirements[:controller]}" unless controller_class && extras.empty?
    options[:controller] = controller_class.controller_path
  end
  @requirements.each {|k,v| options[k] ||= v unless v.kind_of?(Regexp)}

  return nil, "Route recognition didn't find a controller class!" unless controller_class
  return nil, "Unused components were left: #{components.join '/'}" unless components.empty?
  options.delete_if {|k, v| v.nil?} # Remove nil values.
  return controller_class, options
end