Class: Jets::Router::Matcher

Inherits:
Object
  • Object
show all
Extended by:
Memoist
Defined in:
lib/jets/router/matcher.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(route_set = Jets.application.routes) ⇒ Matcher

Returns a new instance of Matcher.



6
7
8
9
# File 'lib/jets/router/matcher.rb', line 6

def initialize(route_set=Jets.application.routes)
  @route_set = route_set
  @routes = route_set.ordered_routes
end

Instance Attribute Details

#routesObject (readonly)

Returns the value of attribute routes.



5
6
7
# File 'lib/jets/router/matcher.rb', line 5

def routes
  @routes
end

Instance Method Details

#constraints_matches?(route) ⇒ Boolean

Matcher called in mimic.rb before mimic event is available We need to extract the parameters from the possible matching route directly since event is unavailable.

Returns:

  • (Boolean)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/jets/router/matcher.rb', line 127

def constraints_matches?(route)
  # Matcher is used super-early when request not yet available.
  # Cannot check constraints because we dont have the request object.
  # To build a request object, would need to build a mimic event and it's not yet available.
  return true if @request.nil?

  constraints = route.constraints
  return true if constraints.blank?

  if constraints.is_a?(Hash)
    parameters = route.extract_parameters(request_path) # extract directly
    constraints.any? do |key, value|
      key = key.to_s
      if value.is_a?(Regexp)
        value.match(parameters[key])
      else # String
        if parameters[key]
          value == parameters[key]
        elsif @request.respond_to?(key)
          value == @request.send(key)
        end
      end
    end
  elsif constraints.respond_to?(:call)
    constraints.call(@request)
  elsif constraints.respond_to?(:matches?)
    constraints.matches?(@request)
  end
end

#find_by_controller_action(controller, action) ⇒ Object



50
51
52
53
54
55
56
# File 'lib/jets/router/matcher.rb', line 50

def find_by_controller_action(controller, action)
  controller = "#{controller.camelize}Controller"
  routes.find do |r|
    r.controller_name == controller.to_s &&
    r.action_name == action.to_s
  end
end

#find_by_env(env) ⇒ Object

Simpler version of find_by_request that does not check constraints. Used by Jets::Controller::Middleware::Mimic and called super-early on. Does not have access to @request object and path_params



43
44
45
46
47
48
# File 'lib/jets/router/matcher.rb', line 43

def find_by_env(env)
  @env = env
  @request_method = env["REQUEST_METHOD"] || "GET"
  @request_path = strip_format(env["PATH_INFO"])
  find_route
end

#find_by_request(request, request_method = nil) ⇒ Object

Precedence:

  1. Routes with no captures get highest precedence: posts/new

  2. Then we consider the routes with captures: post/:id

Within these 2 groups we consider the routes with the longest path first since posts/:id and posts/:id/edit can both match.



30
31
32
33
34
35
36
37
38
# File 'lib/jets/router/matcher.rb', line 30

def find_by_request(request, request_method=nil)
  @request = request # @request is used to check constraints
  # Checking the request_method_from_hidden_method_field so that handler can find the right route
  # super early in the process. Otherwise lambda routes to the wrong controller action.
  @request_method = request_method || @request.request_method_from_hidden_method_field || @request.request_method.to_s.upcase
  @request_path = strip_format(@request.path)
  route = find_route
  match_constraints(route) if route
end

#find_engine_route(route) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/jets/router/matcher.rb', line 71

def find_engine_route(route)
  return unless mount

  engine_matcher = self.class.new(route.engine.route_set)
  engine_route = if @request
                   engine_matcher.find_by_request(@request, @request_method)
                 else
                   engine_matcher.find_by_env(@env)
                 end
  # save original engine for route.extract_parameters later
  engine_route.original_engine = route.engine if engine_route
  engine_route
end

#find_routeObject



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/jets/router/matcher.rb', line 58

def find_route
  routes.each do |r|
    if r.engine
      route = find_engine_route(r)
      return route if route
    else
      found = match?(r)
      return r if found
    end
  end
  nil
end

#match?(route) ⇒ Boolean

Returns:

  • (Boolean)


94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/jets/router/matcher.rb', line 94

def match?(route)
  # Immediately stop checking when the request http_method: GET, POST, ANY, etc
  # doesnt match.
  return false if request_method != route.http_method && route.http_method != "ANY"

  route_path = strip_format(route.path)
  route_path = "#{mount.at}#{route_path}" if mount

  if request_path == route_path
    # regular string match detection
    return true # exact route matches are highest precedence
  end

  # Check path for route capture and wildcard matches:
  # A colon (:) means the variable has a variable
  if route_path.include?(':') # 2nd highest precedence
    capture_detection(route_path, request_path) # early return true or false
  # A star (*) means the variable has a glob
  elsif route_path.include?('*') # lowest precedence
    proxy_detection(route_path, request_path) # early return true or false
  else
    false # reach here, means no route matched
  end
end

#match_constraints(route) ⇒ Object



119
120
121
122
# File 'lib/jets/router/matcher.rb', line 119

def match_constraints(route)
  return unless route
  constraints_matches?(route) ? route : nil
end

#mountObject



85
86
87
# File 'lib/jets/router/matcher.rb', line 85

def mount
  EngineMount.find_by(request_path: request_path)
end

#request_methodObject



20
21
22
# File 'lib/jets/router/matcher.rb', line 20

def request_method
  @request_method.to_s.upcase if @request_method
end

#request_pathObject



11
12
13
14
15
16
17
18
# File 'lib/jets/router/matcher.rb', line 11

def request_path
  # Ensure leading slash
  # Be more forgiving and allow the request_path to be passed in without a leading slash.
  # Note: request PATH_INFO will always have a leading slash, but just in case.
  # This is also covered in specs.
  return unless @request_path
  @request_path.starts_with?('/') ? @request_path : "/#{@request_path}"
end

#strip_format(path) ⇒ Object



90
91
92
# File 'lib/jets/router/matcher.rb', line 90

def strip_format(path)
  path.sub(/\..+$/, '') # Remove format from the end of the path
end