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)


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

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.nil? ? nil : v.to_s)
    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



5
6
7
# File 'lib/action_controller/routing.rb', line 5

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 []



43
44
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/action_controller/routing.rb', line 43

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
      collection = false

      if /^\*/ =~ item.to_s
        collection = true
        item = item.to_s.sub(/^\*/,"").intern
      end

      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.

	    if value.nil? || item == :controller
        value
      elsif collection
        if value.kind_of?(Array)
          value = value.collect {|v| Routing.extract_parameter_value(v)}.join('/')
        else
          value = Routing.extract_parameter_value(value).gsub(/%2F/, "/")
        end
        value
      else
        Routing.extract_parameter_value(value)
      end
    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



151
152
153
154
155
# File 'lib/action_controller/routing.rb', line 151

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



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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/action_controller/routing.rb', line 104

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.to_s
      value = components.empty? ? @defaults[item].clone : components.clone
      value.collect! {|c| CGI.unescape c}
      components = []
      def value.to_s() self.join('/') end
      options[item.to_s.sub(/^\*/,"").intern] = value
    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