feat: extend application with join_controller and join_controller_action
This commit is contained in:
1 parent
b5ea16fa2f
commit
34d1c9bf4e
2 files changed
+116
-2
No files matched your search
@@ -273,6 +273,22 @@ module Prism
|
|||||||
return pattern if prefix_stack.empty?
|
return pattern if prefix_stack.empty?
|
||||||
prefix_stack.join + pattern
|
prefix_stack.join + pattern
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def join_controller(controller)
|
||||||
|
return controller if module_stack.empty?
|
||||||
|
"#{module_stack.join('/')}/#{controller}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def join_controller_action(to)
|
||||||
|
return to unless to
|
||||||
|
controller, action = to.split('#', 2)
|
||||||
|
"#{join_controller(controller)}##{action}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def join_name(name)
|
||||||
|
return name if name.nil? || name_prefix_stack.empty?
|
||||||
|
"#{name_prefix_stack.join('_')}_#{name}".to_sym
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :request, :response, :env
|
attr_reader :request, :response, :env
|
||||||
@@ -285,15 +301,43 @@ module Prism
|
|||||||
head = @request.request_method == 'HEAD'
|
head = @request.request_method == 'HEAD'
|
||||||
lookup_method = head ? 'GET' : @request.request_method
|
lookup_method = head ? 'GET' : @request.request_method
|
||||||
|
|
||||||
|
Orm::NPlusOneDetector.reset! if Orm::NPlusOneDetector.enabled?
|
||||||
|
Instrumentation.instrument('process_action.prism', method: @request.request_method,
|
||||||
|
path: @request.path) do |payload|
|
||||||
|
@instrumentation_payload = payload
|
||||||
catch(:halt) do
|
catch(:halt) do
|
||||||
|
begin
|
||||||
run_before_filters
|
run_before_filters
|
||||||
matched = run_routes(lookup_method)
|
matched = run_routes(lookup_method) || dispatch_mount
|
||||||
handle_not_found unless matched
|
handle_not_found unless matched
|
||||||
|
rescue StandardError => e
|
||||||
|
handle_uncaught_exception(e)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
catch(:halt) do
|
catch(:halt) do
|
||||||
run_after_filters
|
run_after_filters
|
||||||
end
|
end
|
||||||
finalize_flash
|
finalize_flash
|
||||||
|
# Deliberately inside the instrumented span's rescue reach (unlike a
|
||||||
|
# plain StandardError, an NPlusOneViolation is raised AFTER the
|
||||||
|
# action already produced a response) - without this rescue, a
|
||||||
|
# violation would blow straight out of #dispatch, past every
|
||||||
|
# middleware and Server#serve's `@app.call(env)` (server.rb has no
|
||||||
|
# StandardError rescue around that call, only EPIPE/ECONNRESET/
|
||||||
|
# IOError), killing the request thread with the socket closed and
|
||||||
|
# NOT ONE byte written back to the client - silent connection death
|
||||||
|
# instead of the same 500 every other uncaught bug in this app gets.
|
||||||
|
# Routing it through handle_uncaught_exception keeps raise_on_
|
||||||
|
# violation usable as a real regression gate (curl/CI sees a normal
|
||||||
|
# 500, not a broken pipe) instead of only being provable in a unit
|
||||||
|
# test that calls NPlusOneDetector.report! directly.
|
||||||
|
begin
|
||||||
|
Orm::NPlusOneDetector.report!(method: @request.request_method, path: @request.path) if Orm::NPlusOneDetector.enabled?
|
||||||
|
rescue Orm::NPlusOneViolation => e
|
||||||
|
handle_uncaught_exception(e)
|
||||||
|
end
|
||||||
|
payload[:status] = @response.status
|
||||||
|
end
|
||||||
|
|
||||||
status, headers, body = @response.finish
|
status, headers, body = @response.finish
|
||||||
body = [] if head
|
body = [] if head
|
||||||
@@ -335,24 +379,66 @@ module Prism
|
|||||||
end
|
end
|
||||||
|
|
||||||
def dispatch_to_controller(route)
|
def dispatch_to_controller(route)
|
||||||
controller_class = Object.const_get("#{Inflector.camelize(route.controller_name)}Controller")
|
controller_class = resolve_controller_class(route.controller_name)
|
||||||
controller = controller_class.new(@env, route.action_name, @request.path_params)
|
controller = controller_class.new(@env, route.action_name, @request.path_params)
|
||||||
|
@instrumentation_payload&.merge!(controller: route.controller_name, action: route.action_name)
|
||||||
status, headers, body = controller.process
|
status, headers, body = controller.process
|
||||||
@response.status = status
|
@response.status = status
|
||||||
headers.each { |name, value| @response[name] = value }
|
headers.each { |name, value| @response[name] = value }
|
||||||
@response.body = body
|
@response.body = body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dispatch_mount
|
||||||
|
mounted = self.class.router.find_mount(@request.path)
|
||||||
|
return false unless mounted
|
||||||
|
|
||||||
|
prefix, app = mounted
|
||||||
|
path_info = @request.path.delete_prefix(prefix)
|
||||||
|
path_info = '/' if path_info.empty?
|
||||||
|
mount_env = @env.merge(
|
||||||
|
'SCRIPT_NAME' => "#{@env['SCRIPT_NAME']}#{prefix}",
|
||||||
|
'PATH_INFO' => path_info
|
||||||
|
)
|
||||||
|
status, headers, body = app.call(mount_env)
|
||||||
|
@response.status = status
|
||||||
|
headers.each { |name, value| @response[name] = value }
|
||||||
|
@response.body = body
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def finalize_body(result)
|
def finalize_body(result)
|
||||||
|
return if @response.streaming?
|
||||||
return unless @response.body.empty?
|
return unless @response.body.empty?
|
||||||
@response.body = [result.to_s] if result.is_a?(String)
|
@response.body = [result.to_s] if result.is_a?(String)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# "admin/posts" -> Admin::PostsController (each "/"-separated segment
|
||||||
|
# becomes its own camelized module/class, not one flattened name), so
|
||||||
|
# `namespace :admin` dispatches to real nested Ruby modules.
|
||||||
|
def resolve_controller_class(controller_name)
|
||||||
|
segments = controller_name.split('/').map { |segment| Inflector.camelize(segment) }
|
||||||
|
Object.const_get("#{segments.join('::')}Controller")
|
||||||
|
end
|
||||||
|
|
||||||
def handle_not_found
|
def handle_not_found
|
||||||
@response.status = 404
|
@response.status = 404
|
||||||
@response.body = ["Not Found: #{@request.request_method} #{@request.path}"]
|
@response.body = ["Not Found: #{@request.request_method} #{@request.path}"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Last-resort safety net: a controller's own `rescue_from` handlers
|
||||||
|
# (see Controller#handle_exception) already had first crack at this and
|
||||||
|
# re-raised, so nothing in the app knows how to turn it into a response.
|
||||||
|
# Records it on the instrumentation payload (a LogSubscriber can print
|
||||||
|
# it) and renders a generic 500 - never the exception message or
|
||||||
|
# backtrace, which could leak internals to the client.
|
||||||
|
def handle_uncaught_exception(exception)
|
||||||
|
@instrumentation_payload[:exception] = [exception.class.name, exception.message]
|
||||||
|
@instrumentation_payload[:exception_object] = exception
|
||||||
|
@response.status = 500
|
||||||
|
@response['Content-Type'] = 'text/plain'
|
||||||
|
@response.body = ['Internal Server Error']
|
||||||
|
end
|
||||||
|
|
||||||
def load_template(name)
|
def load_template(name)
|
||||||
directory = self.class.settings[:views] || 'views'
|
directory = self.class.settings[:views] || 'views'
|
||||||
File.read(File.join(directory, "#{name}.erb"))
|
File.read(File.join(directory, "#{name}.erb"))
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
require_relative 'assets/manifest'
|
||||||
|
require_relative 'assets/compiler'
|
||||||
|
|
||||||
|
module Prism
|
||||||
|
# Sprockets/Propshaft-lite static asset pipeline: Compiler fingerprints
|
||||||
|
# files on disk into a Manifest, Application#enable_assets makes that
|
||||||
|
# manifest available app-wide so Assets::Helpers (asset_path and friends)
|
||||||
|
# can look logical paths up in it, and Middleware::Assets serves the
|
||||||
|
# fingerprinted files back out with a far-future Cache-Control header
|
||||||
|
# (safe because the filename changes whenever the content does).
|
||||||
|
module Assets
|
||||||
|
class << self
|
||||||
|
attr_writer :manifest
|
||||||
|
|
||||||
|
def manifest
|
||||||
|
@manifest
|
||||||
|
end
|
||||||
|
|
||||||
|
def enabled?
|
||||||
|
!@manifest.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@manifest = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user