lib/socrates/core/dispatcher.rb in socrates-0.1.15 vs lib/socrates/core/dispatcher.rb in socrates-0.1.16
- old
+ new
@@ -3,10 +3,11 @@
require "socrates/configuration"
require "socrates/logger"
require "socrates/string_helpers"
require "socrates/storage/memory"
+require "socrates/core/session"
require "socrates/core/state"
require "socrates/core/state_data"
module Socrates
module Core
@@ -23,29 +24,34 @@
def dispatch(message, context: {})
client_id = @adapter.client_id_from(context: context)
channel = @adapter.channel_from(context: context)
user = @adapter.user_from(context: context)
- do_dispatch(message, client_id, channel, user)
+ session = Session.new(client_id: client_id, channel: channel, user: user)
+
+ do_dispatch(session, message)
end
def start_conversation(user, state_id, message: nil)
client_id = @adapter.client_id_from(user: user)
channel = @adapter.channel_from(user: user)
+ session = Session.new(client_id: client_id, channel: channel, user: user)
+
# Now, we assume the user of this code does this check on their own...
# return false unless conversation_state(user).nil?
# Create state data to match the request.
state_data = Socrates::Core::StateData.new(state_id: state_id, state_action: :ask)
- persist_state_data(client_id, state_data)
+ persist_state_data(session.client_id, state_data)
# Send our initial message if one was passed to us.
- @adapter.send_direct_message(message, user) if message.present?
+ @adapter.send_direct_message(session, message, user) if message.present?
- do_dispatch(nil, client_id, channel, user)
+ do_dispatch(session, nil)
+ true
end
def conversation_state(user)
client_id = @adapter.client_id_from(user: user)
@@ -53,11 +59,11 @@
begin
snapshot = @storage.get(client_id)
state_data = StateData.deserialize(snapshot)
state_data = nil if state_data.expired? || state_data.finished?
- rescue => e
+ rescue StandardError => e
@logger.warn "Error while fetching state_data for client id '#{client_id}'."
@logger.warn e
state_data = nil
end
@@ -66,57 +72,60 @@
private
DEFAULT_ERROR_MESSAGE = "Sorry, an error occurred. We'll have to start over..."
- def do_dispatch(message, client_id, channel, user)
+ # rubocop:disable Metrics/AbcSize
+ def do_dispatch(session, message)
message = message&.strip
- @logger.info %Q(#{client_id} recv: "#{message}")
+ @logger.info %Q(#{session.client_id} recv: "#{message}")
# In many cases, a two actions will run in this loop: :listen => :ask, but it's possible that a chain of 2 or
# more :ask actions could run, before stopping at a :listen (and waiting for the next input).
loop do
- state_data = fetch_state_data(client_id)
- state = instantiate_state(state_data, channel, user)
+ state_data = fetch_state_data(session.client_id)
+ state = instantiate_state(session, state_data)
args = [state.data.state_action]
args << message if state.data.state_action == :listen
- msg = "#{client_id} processing :#{state.data.state_id} / :#{args.first}"
+ msg = "#{session.client_id} processing :#{state.data.state_id} / :#{args.first}"
msg += %Q( / message: "#{args.second}") if args.count > 1
@logger.debug msg
begin
state.send(*args)
- rescue => e
- handle_action_error(e, client_id, state, channel)
+ rescue StandardError => e
+ handle_action_error(e, session, state)
return
end
# Update the persisted state data so we know what to run next time.
state.data.state_id = state.next_state_id
state.data.state_action = state.next_state_action
- @logger.debug "#{client_id} transition to :#{state.data.state_id} / :#{state.data.state_action}"
+ @logger.debug "#{session.client_id} transition to :#{state.data.state_id} / :#{state.data.state_action}"
- persist_state_data(client_id, state.data)
+ persist_state_data(session.client_id, state.data)
# Break from the loop if there's nothing left to do, i.e. no more state transitions.
break if done_transitioning?(state)
end
+ # rubocop:enable Metrics/AbcSize
- true
+ # Flush the session, which contains any not-yet-send messages.
+ @adapter.flush_session(session)
end
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
def fetch_state_data(client_id)
if @storage.has_key?(client_id)
begin
snapshot = @storage.get(client_id)
state_data = StateData.deserialize(snapshot)
- rescue => e
+ rescue StandardError => e
@logger.warn "Error while fetching state_data for client id '#{client_id}', resetting state: #{e.message}"
@logger.warn e
end
end
@@ -145,31 +154,32 @@
def persist_state_data(client_id, state_data)
state_data.reset_elapsed_time
@storage.put(client_id, state_data.serialize)
end
- def instantiate_state(state_data, channel, user)
- @state_factory.build(state_data: state_data, adapter: @adapter, channel: channel, user: user)
+ def instantiate_state(session, state_data)
+ @state_factory.build(state_data: state_data, adapter: @adapter, session: session)
end
def done_transitioning?(state)
# Stop transitioning if we're waiting for the user to respond (i.e. we're listening).
return true if state.data.state_action == :listen
# Stop transitioning if there's no state to transition to, or the conversation has ended.
state.data.state_id.nil? || state.data.state_id == StateData::END_OF_CONVERSATION
end
- def handle_action_error(e, client_id, state, channel)
+ def handle_action_error(e, session, state)
@logger.warn "Error while processing action #{state.data.state_id}/#{state.data.state_action}: #{e.message}"
@logger.warn e
- @adapter.send_message(@error_message, channel)
+ @adapter.send_message(session, @error_message, send_now: true)
+
state.data.clear
state.data.state_id = nil
state.data.state_action = nil
- persist_state_data(client_id, state.data)
+ persist_state_data(session.client_id, state.data)
end
end
end
end