Class: ActionController::Routing::RouteBuilder

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

Overview

:nodoc:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRouteBuilder

Returns a new instance of RouteBuilder.



790
791
792
793
# File 'lib/action_controller/routing.rb', line 790

def initialize
  self.separators = Routing::SEPARATORS
  self.optional_separators = %w( / )
end

Instance Attribute Details

#optional_separatorsObject

Returns the value of attribute optional_separators.



788
789
790
# File 'lib/action_controller/routing.rb', line 788

def optional_separators
  @optional_separators
end

#separatorsObject

Returns the value of attribute separators.



788
789
790
# File 'lib/action_controller/routing.rb', line 788

def separators
  @separators
end

Instance Method Details

#assign_default_route_options(segments) ⇒ Object

Assign default options, such as ‘index’ as a default for :action. This method must be run after user supplied requirements and defaults have been applied to the segments.



899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
# File 'lib/action_controller/routing.rb', line 899

def assign_default_route_options(segments)
  segments.each do |segment|
    next unless segment.is_a? DynamicSegment
    case segment.key
      when :action
        if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
          segment.default ||= 'index'
          segment.is_optional = true
        end
      when :id
        if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
          segment.is_optional = true
        end
    end
  end
end

#assign_route_options(segments, defaults, requirements) ⇒ Object

Takes a hash of defaults and a hash of requirements, and assigns them to the segments. Any unused requirements (which do not correspond to a segment) are returned as a hash.



864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
# File 'lib/action_controller/routing.rb', line 864

def assign_route_options(segments, defaults, requirements)
  route_requirements = {} # Requirements that do not belong to a segment
  
  segment_named = Proc.new do |key|
    segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
  end
  
  requirements.each do |key, requirement|
    segment = segment_named[key]
    if segment
      raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
      if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
        raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
      end
      segment.regexp = requirement
    else
      route_requirements[key] = requirement
    end
  end
  
  defaults.each do |key, default|
    segment = segment_named[key]
    raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
    segment.is_optional = true
    segment.default = default.to_param if default
  end
  
  assign_default_route_options(segments)
  ensure_required_segments(segments)
  route_requirements
end

#build(path, options) ⇒ Object

Construct and return a route with the given path and options.



936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
# File 'lib/action_controller/routing.rb', line 936

def build(path, options)
  # Wrap the path with slashes
  path = "/#{path}" unless path[0] == ?/
  path = "#{path}/" unless path[-1] == ?/
    
  segments = segments_for_route_path(path)
  defaults, requirements, conditions = divide_route_options(segments, options)
  requirements = assign_route_options(segments, defaults, requirements)

  route = Route.new
  route.segments = segments
  route.requirements = requirements
  route.conditions = conditions

  if !route.significant_keys.include?(:action) && !route.requirements[:action]
    route.requirements[:action] = "index"
    route.significant_keys << :action
  end

  if !route.significant_keys.include?(:controller)
    raise ArgumentError, "Illegal route: the :controller must be specified!"
  end

  route
end

#divide_route_options(segments, options) ⇒ Object

Split the given hash of options into requirement and default hashes. The segments are passed alongside in order to distinguish between default values and requirements.



846
847
848
849
850
851
852
853
854
855
856
857
858
859
# File 'lib/action_controller/routing.rb', line 846

def divide_route_options(segments, options)
  options = options.dup
  requirements = (options.delete(:requirements) || {}).dup
  defaults     = (options.delete(:defaults)     || {}).dup
  conditions   = (options.delete(:conditions)   || {}).dup

  path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
  options.each do |key, value|
    hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
    hash[key] = value
  end
    
  [defaults, requirements, conditions]
end

#ensure_required_segments(segments) ⇒ Object

Makes sure that there are no optional segments that precede a required segment. If any are found that precede a required segment, they are made required.



919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
# File 'lib/action_controller/routing.rb', line 919

def ensure_required_segments(segments)
  allow_optional = true
  segments.reverse_each do |segment|
    allow_optional &&= segment.optional?
    if !allow_optional && segment.optional?
      unless segment.optionality_implied?
        warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
      end
      segment.is_optional = false
    elsif allow_optional & segment.respond_to?(:default) && segment.default
      # if a segment has a default, then it is optional
      segment.is_optional = true
    end
  end
end

#interval_regexpObject



799
800
801
# File 'lib/action_controller/routing.rb', line 799

def interval_regexp
  Regexp.new "(.*?)(#{separators.source}|$)"
end

#segment_for(string) ⇒ Object

A factory method that returns a new segment instance appropriate for the format of the given string.



821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
# File 'lib/action_controller/routing.rb', line 821

def segment_for(string)
  segment = case string
    when /\A:(\w+)/
      key = $1.to_sym
      case key
        when :controller then ControllerSegment.new(key)
        else DynamicSegment.new key
      end
    when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
    when /\A\?(.*?)\?/
      returning segment = StaticSegment.new($1) do
        segment.is_optional = true
      end
    when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
    when Regexp.new(separator_pattern) then
      returning segment = DividerSegment.new($&) do
        segment.is_optional = (optional_separators.include? $&)
      end
  end
  [segment, $~.post_match]
end

#segments_for_route_path(path) ⇒ Object

Accepts a “route path” (a string defining a route), and returns the array of segments that corresponds to it. Note that the segment array is only partially initialized–the defaults and requirements, for instance, need to be set separately, via the #assign_route_options method, and the #optional? method for each segment will not be reliable until after #assign_route_options is called, as well.



809
810
811
812
813
814
815
816
817
# File 'lib/action_controller/routing.rb', line 809

def segments_for_route_path(path)
  rest, segments = path, []
    
  until rest.empty?
    segment, rest = segment_for rest
    segments << segment
  end
  segments
end

#separator_pattern(inverted = false) ⇒ Object



795
796
797
# File 'lib/action_controller/routing.rb', line 795

def separator_pattern(inverted = false)
  "[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
end