Class: Otto::RouteHandlers::LogicClassHandler

Inherits:
BaseHandler
  • Object
show all
Defined in:
lib/otto/route_handlers/logic_class.rb

Overview

Handler for Logic classes (new in Otto Framework Enhancement)

Logic classes use a constrained signature: initialize(context, params, locale)

  • context: The authentication strategy result (user info, session data)
  • params: Merged request parameters. Path captures win over form and query parameters (Rack order between those two), and a JSON body sits below all of them.
  • locale: The locale string from env

A Logic class may also declare a route_params: keyword on initialize to receive the path captures on their own, separate from anything the caller sent in the query string or body:

def initialize(context, params, locale, route_params: {})

IMPORTANT: Logic classes do NOT receive the Rack request or env hash. This is intentional - Logic classes work with clean, authenticated contexts. For endpoints requiring direct request access (sessions, cookies, headers, or logout flows), use controller handlers (Controller#action or Controller.action).

Constant Summary collapse

ROUTE_PARAMS_KEYWORD_TYPES =

Parameter types (from Method#parameters) that name a keyword argument

%i[key keyreq].freeze

Instance Attribute Summary

Attributes inherited from BaseHandler

#otto_instance, #route_definition

Instance Method Summary collapse

Methods inherited from BaseHandler

#call, #finalize_response, #handle_execution_error, #handle_local_error, #handle_response, #initialize, #setup_request_response, #target_class

Constructor Details

This class inherits a constructor from Otto::RouteHandlers::BaseHandler

Instance Method Details

#accepts_route_params? ⇒ Boolean (protected)

Whether the Logic class constructor declares a route_params: keyword (or accepts arbitrary keywords). Logic classes opt in by declaring it; the three-positional signature keeps working unchanged.

Returns:

  • (Boolean)


91
92
93
94
95
96
97
# File 'lib/otto/route_handlers/logic_class.rb', line 91

def accepts_route_params?
  return @accepts_route_params if defined?(@accepts_route_params)

  @accepts_route_params = target_class.instance_method(:initialize).parameters.any? do |type, name|
    type == :keyrest || (ROUTE_PARAMS_KEYWORD_TYPES.include?(type) && name == :route_params)
  end
end

#extract_logic_params(req, env) ⇒ Hash (protected)

Extract logic parameters including JSON body parsing

Starts from req.params, which already carries Rack's own precedence (form body over query string) with the path captures merged on top by setup_request_response. A JSON body sits below all of those: a JSON key can never replace a path capture, a query parameter, or a form field. JSON bodies are only read for methods that carry a body (never GET or HEAD).

Parameters:

  • req (Rack::Request) —

    Request object

  • env (Hash) —

    Rack environment

Returns:

  • (Hash) —

    Parameters for Logic class



111
112
113
114
115
116
117
118
119
# File 'lib/otto/route_handlers/logic_class.rb', line 111

def extract_logic_params(req, env)
  logic_params = req.params.dup
  return logic_params unless json_body?(req)

  json_params = parse_json_body(req, env)
  return logic_params if json_params.empty?

  Otto::Static.indifferent_params(json_params.merge(logic_params))
end

#handler_name ⇒ String (protected)

Format handler name for Logic routes

Returns:

  • (String) —

    Handler name in format "ClassName#call"



160
161
162
# File 'lib/otto/route_handlers/logic_class.rb', line 160

def handler_name
  "#{target_class.name}#call"
end

#invoke_target(req, _res) ⇒ Array (protected)

Invoke Logic class with constrained signature

Parameters:

  • req (Rack::Request) —

    Request object

  • res (Rack::Response) —

    Response object

Returns:

  • (Array) —

    [result, context] for handle_response



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/otto/route_handlers/logic_class.rb', line 38

def invoke_target(req, _res)
  env = req.env

  # Get strategy result (guaranteed to exist from RouteAuthWrapper)
  strategy_result = env['otto.strategy_result']

  # Extract params including JSON body parsing
  logic_params = extract_logic_params(req, env)

  # Get locale
  locale = env['otto.locale'] || 'en'

  # Instantiate Logic class. Path captures travel separately so a Logic
  # class can read the router's value by name, whatever the body sent.
  logic = if accepts_route_params?
            target_class.new(strategy_result, logic_params, locale, route_params: route_params)
          else
            target_class.new(strategy_result, logic_params, locale)
          end

  # Execute standard Logic class lifecycle
  logic.raise_concerns if logic.respond_to?(:raise_concerns)

  result = if logic.respond_to?(:process)
             logic.process
           else
             logic.call || logic
           end

  context = {
    logic_instance: logic,
           request: req,
       status_code: logic.respond_to?(:status_code) ? logic.status_code : nil,
  }

  [result, context]
end

#json_body?(req) ⇒ Boolean (protected)

Whether the request carries a JSON body worth parsing

Parameters:

  • req (Rack::Request) —

    Request object

Returns:

  • (Boolean)


124
125
126
127
128
129
# File 'lib/otto/route_handlers/logic_class.rb', line 124

def json_body?(req)
  return false if req.get? || req.head?
  return false unless req.content_type&.include?('application/json')

  req.body&.size&.positive? || false
end

#parse_json_body(req, env) ⇒ Hash (protected)

Parse JSON request body with error handling

Parameters:

  • req (Rack::Request) —

    Request object

  • env (Hash) —

    Rack environment

Returns:

  • (Hash) —

    Parsed JSON object, or an empty hash when the body is not a JSON object or fails to parse



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/otto/route_handlers/logic_class.rb', line 136

def parse_json_body(req, env)
  req.body.rewind
  json_data = JSON.parse(req.body.read)
  json_data.is_a?(Hash) ? json_data : {}
rescue JSON::ParserError => e
  # Base context pattern: create once, reuse for correlation
  log_context = Otto::LoggingHelpers.request_context(env)

  Otto.structured_log(:error, 'JSON parsing error',
    log_context.merge(
      handler: handler_name,
      error: e.message,
      error_class: e.class.name,
      duration: Otto::Utils.now_in_μs - @start_time
    ))

  Otto::LoggingHelpers.log_backtrace(e,
    log_context.merge(handler: handler_name))

  {}
end

#route_params ⇒ Hash (protected)

Path captures matched by the router, keyed by the route's placeholder names. Always present in params as well (where they take precedence); this copy is for Logic classes that must not confuse a path value with one the caller put in the query string or body.

Returns:

  • (Hash) —

    Indifferent hash of route captures; empty for literal routes



82
83
84
# File 'lib/otto/route_handlers/logic_class.rb', line 82

def route_params
  Otto::Static.indifferent_params((@extra_params || {}).dup)
end