Class: Rackr::Router

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/rackr/router.rb,
lib/rackr/router/route.rb,
lib/rackr/router/errors.rb,
lib/rackr/router/endpoint.rb,
lib/rackr/router/path_route.rb,
lib/rackr/router/build_request.rb,
lib/rackr/router/dev_html/dump.rb,
lib/rackr/router/dev_html/errors.rb

Overview

This is the core class of Rackr. This class aggregate the route instance tree, callbacks (before and after) and scopes then, using the building blocks, match the request and call the endpoints

Defined Under Namespace

Modules: DevHtml, Endpoint, Errors Classes: BuildRequest, PathRoute, Route

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#deep_hash_push, #deep_hash_set, #ensure_array

Constructor Details

#initialize(config = {}, before: [], after: []) ⇒ Router

Returns a new instance of Router.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/rackr/router.rb', line 20

def initialize(config = {}, before: [], after: [])
  http_methods = HTTP_METHODS.map { |m| m.downcase.to_sym }
  @routes = Struct.new(*http_methods).new
  http_methods.each do |method|
    @routes.send("#{method}=", Hash.new do |_hash, key|
      raise(Errors::UndefinedNamedRouteError, "Undefined named route: '#{key}'")
    end)
  end
  @dev_mode = ENV['RACK_ENV'] == 'development'
  @config = config
  @scopes = []
  @befores = ensure_array(before)
  @scopes_befores = {}
  @empty_scopes_ids = 0
  @afters = ensure_array(after)
  @scopes_afters = {}
  @path_routes_tree = {}
  %w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |method|
    @path_routes_tree[method] = { __instances: [] }
  end
  @not_found_tree = {}
  @default_not_found =
    Route.new(
      proc { [404, {}, ['Not found']] },
      befores: @befores,
      afters: @afters
    )
  @error_tree = {}
  @default_error =
    Route.new(
      proc { |_req, _e| [500, {}, ['Internal server error']] },
      befores: @befores,
      afters: @afters
    )
  @specific_error_tree = {}
  @specific_errors = {}
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



18
19
20
# File 'lib/rackr/router.rb', line 18

def config
  @config
end

#default_not_found=(value) ⇒ Object (writeonly)

Sets the attribute default_not_found

Parameters:

  • value

    the value to set the attribute default_not_found to.



17
18
19
# File 'lib/rackr/router.rb', line 17

def default_not_found=(value)
  @default_not_found = value
end

#error_treeObject (readonly)

Returns the value of attribute error_tree.



18
19
20
# File 'lib/rackr/router.rb', line 18

def error_tree
  @error_tree
end

#not_found_treeObject (readonly)

Returns the value of attribute not_found_tree.



18
19
20
# File 'lib/rackr/router.rb', line 18

def not_found_tree
  @not_found_tree
end

#routesObject (readonly)

Returns the value of attribute routes.



18
19
20
# File 'lib/rackr/router.rb', line 18

def routes
  @routes
end

#specific_error_treeObject (readonly)

Returns the value of attribute specific_error_tree.



18
19
20
# File 'lib/rackr/router.rb', line 18

def specific_error_tree
  @specific_error_tree
end

Instance Method Details

#add(method, path, endpoint, as: nil, route_befores: [], route_afters: []) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/rackr/router.rb', line 153

def add(method, path, endpoint, as: nil, route_befores: [], route_afters: [])
  Errors.check_path(path)
  Errors.check_endpoint(endpoint, path)
  Errors.check_as(as, path)
  Errors.check_callbacks(route_befores, path)
  Errors.check_callbacks(route_afters, path)

  method = :get if method == :head

  wildcard = path == '*'
  path = path.is_a?(Symbol) ? path.inspect : path.delete_prefix('/')
  path_with_scopes = "/#{not_empty_scopes.join('/')}#{put_path_slash(path)}"
  add_named_route(method, path_with_scopes, as)
  action_befores, action_afters = fetch_endpoint_callbacks(endpoint)

  route_instance =
    PathRoute.new(
      path_with_scopes,
      endpoint,
      befores: @befores + ensure_array(route_befores) + action_befores,
      afters: @afters + ensure_array(route_afters) + action_afters,
      wildcard: wildcard
    )

  path_segments = path_with_scopes.split('/').reject(&:empty?)

  if path_segments.empty?
    @path_routes_tree[method.to_s.upcase][:__instances].push(route_instance)
  else
    deep_hash_push(@path_routes_tree[method.to_s.upcase], *(path_segments + [:__instances]), route_instance)
  end
end

#add_error(endpoint, error_class = nil) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/rackr/router.rb', line 202

def add_error(endpoint, error_class = nil)
  Errors.check_endpoint(endpoint, 'error')

  action_befores, action_afters = fetch_endpoint_callbacks(endpoint)
  route_instance =
    Route.new(
      endpoint,
      befores: @befores + action_befores,
      afters: @afters + action_afters
    )

  if error_class
    return set_to_scope(specific_error_tree[error_class] ||= {}, route_instance) if @scopes.size >= 1

    @specific_errors[error_class] = route_instance
  else
    return set_to_scope(error_tree, route_instance) if @scopes.size >= 1

    @default_error = route_instance
  end
end

#add_not_found(endpoint) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/rackr/router.rb', line 186

def add_not_found(endpoint)
  Errors.check_endpoint(endpoint, 'not found')

  action_befores, action_afters = fetch_endpoint_callbacks(endpoint)
  route_instance =
    Route.new(
      endpoint,
      befores: @befores + action_befores,
      afters: @afters + action_afters
    )

  return set_to_scope(not_found_tree, route_instance) if @scopes.size >= 1

  @default_not_found = route_instance
end

#append_scope(name, scope_befores: [], scope_afters: []) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/rackr/router.rb', line 224

def append_scope(name, scope_befores: [], scope_afters: [])
  Errors.check_scope_name(name)
  Errors.check_scope_slashes(name)
  Errors.check_callbacks(scope_befores, name)
  Errors.check_callbacks(scope_afters, name)

  name = ":#{name}" if name.is_a? Symbol
  if name == ''
    @empty_scopes_ids = @empty_scopes_ids + 1
    name = @empty_scopes_ids
  end

  @scopes.push(name)

  scope_befores = ensure_array(scope_befores)
  @befores.concat(scope_befores)
  @scopes_befores[name] = scope_befores

  scope_afters = ensure_array(scope_afters)
  @afters.concat(scope_afters)
  @scopes_afters[name] = scope_afters
end

#call(env) ⇒ Object



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
100
101
102
103
# File 'lib/rackr/router.rb', line 58

def call(env)
  path_info = env['PATH_INFO']
  request_method = env['REQUEST_METHOD'] == 'HEAD' ? 'GET' : env['REQUEST_METHOD']

  splitted_request_path_info = path_info.split('/')
  current_request_path_info =
    path_info == '/' ? path_info : path_info.chomp('/') # remove trailing "/"

  route_instance, found_scopes = match_path_route(
    request_method,
    splitted_request_path_info,
    current_request_path_info
  )
  rack_request = BuildRequest.new(env, splitted_request_path_info).call(route_instance)
  befores = route_instance.befores
  before_result = nil

  begin
    i = 0
    while i < befores.size
      before_result = Endpoint.call(befores[i], rack_request, @routes, @config)
      unless before_result.is_a?(Rack::Request)
        Errors.check_rack_response(before_result, 'before callback')

        return before_result
      end

      i += 1
    end

    endpoint_result = Endpoint.call(route_instance.endpoint, before_result || rack_request, @routes, @config)

    call_afters(route_instance, endpoint_result)
  rescue Rackr::NotFound
    return not_found_fallback(found_scopes, route_instance, before_result || rack_request)
  rescue Rackr::Dump => e
    return Endpoint.call(DevHtml::Dump, env.merge({ 'dump' => e }))
    # rubocop:disable Lint/RescueException
  rescue Exception => e
    return error_fallback(found_scopes, route_instance, before_result || rack_request, e, env)
  end
  # rubocop:enable Lint/RescueException

  Errors.check_rack_response(endpoint_result, 'action')
  endpoint_result
end

#call_afters(route_instance, endpoint_result) ⇒ Object



144
145
146
147
148
149
150
151
# File 'lib/rackr/router.rb', line 144

def call_afters(route_instance, endpoint_result)
  afters = route_instance.afters
  i = 0
  while i < afters.size
    Endpoint.call(afters[i], endpoint_result, @routes, @config)
    i += 1
  end
end

#clear_last_scopeObject



247
248
249
250
251
# File 'lib/rackr/router.rb', line 247

def clear_last_scope
  @befores -= @scopes_befores[@scopes.last]
  @afters -= @scopes_afters[@scopes.last]
  @scopes = @scopes.first(@scopes.size - 1)
end

#error_fallback(found_scopes, route_instance, request, error, env) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rackr/router.rb', line 122

def error_fallback(found_scopes, route_instance, request, error, env)
  error_route = match_route(
    found_scopes,
    specific_error_tree[error.class] || error_tree,
    @specific_errors[error.class] || @default_error
  )

  return Endpoint.call(DevHtml::Errors, env.merge({ 'error' => error })) if @dev_mode && error_route == @default_error

  endpoint_result = Endpoint.call(error_route.endpoint, request, @routes, @config, error)

  if endpoint_result.nil?
    return Endpoint.call(DevHtml::Errors, env.merge({ 'error' => error })) if @dev_mode

    endpoint_result = Endpoint.call(@default_error.endpoint, request, @routes, @config, error)
  end

  call_afters(route_instance, endpoint_result)

  endpoint_result
end

#not_empty_scopesObject



253
254
255
# File 'lib/rackr/router.rb', line 253

def not_empty_scopes
  @scopes.reject { |s| s.is_a?(Integer) }
end

#not_found_fallback(found_scopes, route_instance, request) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/rackr/router.rb', line 105

def not_found_fallback(found_scopes, route_instance, request)
  endpoint_result = Endpoint.call(
    match_route(
      found_scopes,
      not_found_tree,
      @default_not_found
    ).endpoint,
    request,
    @routes,
    @config
  )

  call_afters(route_instance, endpoint_result)

  endpoint_result
end