Class: FlowChat::Whatsapp::Middleware::ChoiceMapper

Inherits:
Object
  • Object
show all
Defined in:
lib/flow_chat/whatsapp/middleware/choice_mapper.rb

Overview

Maps what WhatsApp sends back to the choice key the flow branches on.

The value on the wire is the title the user was shown, so a tap and a user typing what they read arrive as the same string and resolve through the same map.

Flow:

  1. Flow returns choices with original keys (=> "Create Account")
  2. This middleware asks FlowChat::ChoiceTitles for each displayed title
  3. It re-keys the choices by title and stores title => original key
  4. Renderer receives the re-keyed choices and renders them
  5. User taps a button, or types the title on it
  6. This middleware resolves either back to the original key

A title is truncated to the rung's cap (see FlowChat::Whatsapp::Renderer::BUTTON_TITLE_LENGTH / LIST_ROW_TITLE_LENGTH). When truncation, or a duplicate label, would leave two titles indistinguishable, FlowChat::ChoiceTitles numbers every title in the set instead - which is what keeps them distinct, and why nothing here has to check for collisions itself. See its docs for why that decision is made once for the whole set.

Examples:

# Flow provides: {"create" => "Create Account"}
# Titles built:  "Create Account"
# Transformed:   {"Create Account" => "Create Account"}
# Mapping:       {"Create Account" => "create"}

# With duplicates: {"yes" => "Accept", "no" => "Accept"}
# Titles built:  "1. Accept", "2. Accept"
# Mapping:       {"1. Accept" => "yes", "2. Accept" => "no"}

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ ChoiceMapper

Returns a new instance of ChoiceMapper.



37
38
39
40
# File 'lib/flow_chat/whatsapp/middleware/choice_mapper.rb', line 37

def initialize(app)
  @app = app
  FlowChat.logger.debug { "Whatsapp::ChoiceMapper: Initialized WhatsApp choice mapping middleware" }
end

Instance Method Details

#call(context) ⇒ Object



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
75
76
77
78
79
80
81
82
83
84
# File 'lib/flow_chat/whatsapp/middleware/choice_mapper.rb', line 42

def call(context)
  @context = context
  @session = context.session

  session_id = context["session.id"]
  FlowChat.logger.debug { "Whatsapp::ChoiceMapper: Processing request for session #{session_id}" }

  if intercept?
    FlowChat.logger.info { "Whatsapp::ChoiceMapper: Intercepting request for choice resolution - session #{session_id}" }
    handle_choice_input
  end

  # The maps belong to exactly one screen: this turn's, if it had a
  # resolvable answer, or one that already fell out of use otherwise.
  # Either way nothing here is still owed to the next screen, so they
  # are cleared unconditionally rather than asked whether they still
  # look "live" - create_id_mapping immediately below repopulates
  # them whenever the app actually returns choices.
  #
  # An earlier version asked should_clear_for_new_flow? that question
  # after handle_choice_input had already rewritten @context.input to
  # the *resolved* value, which can equal one of the map's own keys
  # (an Array choice's key is its label, and the wire value is the
  # title built from that label), so the check answered
  # "still live" about a value that was never a fresh reply. That let
  # the maps survive into a free-text screen and reinterpret a typed
  # answer there as the previous menu's choice. This was fixed once
  # here in Task 6 and once for Messenger in Task 13; both fixes had
  # the same shape and the same blind spot, which is why the guard is
  # gone rather than patched a third time.
  clear_choice_state

  # Call the app (executor -> flow)
  type, prompt, choices, media = @app.call(context)

  # Transform choices if present (like USSD does)
  if choices.present?
    FlowChat.logger.debug { "Whatsapp::ChoiceMapper: Found choices, creating ID mapping" }
    choices = create_id_mapping(choices)
  end

  [type, prompt, choices, media]
end