aboutsummaryrefslogblamecommitdiffstats
path: root/actionpack/lib/action_dispatch/journey/route.rb
blob: 8165709a3da015092bad14a4d08aa36ab879a547 (plain) (tree)
1
2
3
4
5
6
7
8
9

                             
                     


                
                                                            
 
                                         

                                    


                                                                              




                                                         














                                                                  
                                

           
                                                                        




                                            







                                                              
                                                                               
                                                                                
                                                                                              

         


                                                                           
                                                                                                                                   


                           
 
                                                    
                                  

                                
                                               


                                
                                       
                                                  
                                     

         







                         
             
                                
                                  
                                                                        
                       
           

         







                                                                             
                      
                                                             




                    
                  


                       
                                               

         
                              
                                           
 







                                                    
 



                                              
                                         


                                
                              
                                             

         
                        
                                                             

         
                                
                                         

         
                           
                                                                
                                                    

           
 



                                           
                     
                        

         
                           

                                          




                                                



                                         


                                          
         





                              
                                 
                                                                  

         
              
                       


             


                                           
 


                                                           

       
              
   
# frozen_string_literal: true

module ActionDispatch
  # :stopdoc:
  module Journey
    class Route
      attr_reader :app, :path, :defaults, :name, :precedence

      attr_reader :constraints, :internal
      alias :conditions :constraints

      module VerbMatchers
        VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
        VERBS.each do |v|
          class_eval <<-eoc, __FILE__, __LINE__ + 1
            class #{v}
              def self.verb; name.split("::").last; end
              def self.call(req); req.#{v.downcase}?; end
            end
          eoc
        end

        class Unknown
          attr_reader :verb

          def initialize(verb)
            @verb = verb
          end

          def call(request); @verb === request.request_method; end
        end

        class All
          def self.call(_); true; end
          def self.verb; ""; end
        end

        VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash|
          klass = const_get verb
          hash[verb]                 = klass
          hash[verb.downcase]        = klass
          hash[verb.downcase.to_sym] = klass
        end
      end

      def self.verb_matcher(verb)
        VerbMatchers::VERB_TO_CLASS.fetch(verb) do
          VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
        end
      end

      def self.build(name, app, path, constraints, required_defaults, defaults)
        request_method_match = verb_matcher(constraints.delete(:request_method))
        new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
      end

      ##
      # +path+ is a path constraint.
      # +constraints+ is a hash of constraints to be applied to this route.
      def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false)
        @name        = name
        @app         = app
        @path        = path

        @request_method_match = request_method_match
        @constraints = constraints
        @defaults    = defaults
        @required_defaults = nil
        @_required_defaults = required_defaults
        @required_parts    = nil
        @parts             = nil
        @decorated_ast     = nil
        @precedence        = precedence
        @path_formatter    = @path.build_formatter
        @internal          = internal
      end

      def eager_load!
        path.eager_load!
        ast
        parts
        required_defaults
        nil
      end

      def ast
        @decorated_ast ||= begin
          decorated_ast = path.ast
          decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
          decorated_ast
        end
      end

      # Needed for `rails routes`. Picks up succinctly defined requirements
      # for a route, for example route
      #
      #   get 'photo/:id', :controller => 'photos', :action => 'show',
      #     :id => /[A-Z]\d{5}/
      #
      # will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/}
      # as requirements.
      def requirements
        @defaults.merge(path.requirements).delete_if { |_, v|
          /.+?/ == v
        }
      end

      def segments
        path.names
      end

      def required_keys
        required_parts + required_defaults.keys
      end

      def score(supplied_keys)
        required_keys = path.required_names

        required_keys.each do |k|
          return -1 unless supplied_keys.include?(k)
        end

        score = 0
        path.names.each do |k|
          score += 1 if supplied_keys.include?(k)
        end

        score + (required_defaults.length * 2)
      end

      def parts
        @parts ||= segments.map(&:to_sym)
      end
      alias :segment_keys :parts

      def format(path_options)
        @path_formatter.evaluate path_options
      end

      def required_parts
        @required_parts ||= path.required_names.map(&:to_sym)
      end

      def required_default?(key)
        @_required_defaults.include?(key)
      end

      def required_defaults
        @required_defaults ||= @defaults.dup.delete_if do |k, _|
          parts.include?(k) || !required_default?(k)
        end
      end

      def glob?
        !path.spec.grep(Nodes::Star).empty?
      end

      def dispatcher?
        @app.dispatcher?
      end

      def matches?(request)
        match_verb(request) &&
        constraints.all? { |method, value|
          case value
          when Regexp, String
            value === request.send(method).to_s
          when Array
            value.include?(request.send(method))
          when TrueClass
            request.send(method).present?
          when FalseClass
            request.send(method).blank?
          else
            value === request.send(method)
          end
        }
      end

      def ip
        constraints[:ip] || //
      end

      def requires_matching_verb?
        !@request_method_match.all? { |x| x == VerbMatchers::All }
      end

      def verb
        verbs.join("|")
      end

      private
        def verbs
          @request_method_match.map(&:verb)
        end

        def match_verb(request)
          @request_method_match.any? { |m| m.call request }
        end
    end
  end
  # :startdoc:
end