lib/socrates/core/dispatcher.rb in socrates-0.1.10 vs lib/socrates/core/dispatcher.rb in socrates-0.1.11
- old
+ new
@@ -18,64 +18,100 @@
@logger = Socrates.config.logger
@error_message = Socrates.config.error_message || DEFAULT_ERROR_MESSAGE
end
- # rubocop:disable Metrics/AbcSize
def dispatch(message, context: {})
- message = message.strip
+ client_id = @adapter.client_id_from(context: context)
+ channel = @adapter.channel_from(context: context)
- client_id = @adapter.client_id_from_context(context)
+ do_dispatch(message, client_id, channel)
+ end
- @logger.info %(#{client_id} recv: "#{message}")
+ def start_conversation(user, state_id)
+ client_id = @adapter.client_id_from(user: user)
+ channel = @adapter.channel_from(user: user)
+ 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)
+
+ do_dispatch(nil, client_id, channel)
+ true
+ end
+
+ def conversation_state(user)
+ client_id = @adapter.client_id_from(user: user)
+
+ return nil unless @storage.has_key?(client_id)
+
+ begin
+ snapshot = @storage.get(client_id)
+ state_data = StateData.deserialize(snapshot)
+ state_data = nil if state_data_expired?(state_data)
+ rescue => e
+ @logger.warn "Error while fetching state_data for client id '#{client_id}'."
+ @logger.warn e
+ state_data = nil
+ end
+
+ state_data
+ end
+
+ private
+
+ DEFAULT_ERROR_MESSAGE = "Sorry, an error occurred. We'll have to start over..."
+
+ def do_dispatch(message, client_id, channel)
+ message = message&.strip
+
+ @logger.info %Q(#{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_snapshot(client_id)
- state = instantiate_state(state_data, context)
+ state_data = fetch_state_data(client_id)
+ state = instantiate_state(state_data, channel)
args = [state.data.state_action]
args << message if state.data.state_action == :listen
- msg = %(#{client_id} processing :#{state.data.state_id} / :#{args.first})
- msg += %( / message: "#{args.second}") if args.count > 1
+ msg = "#{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, context)
+ handle_action_error(e, client_id, state, channel)
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 "#{client_id} transition to :#{state.data.state_id} / :#{state.data.state_action}"
- persist_snapshot(client_id, state.data)
+ persist_state_data(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
end
- # rubocop:enable Metrics/AbcSize
- private
-
- DEFAULT_ERROR_MESSAGE = "Sorry, an error occurred. We'll have to start over..."
-
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
- def fetch_snapshot(client_id)
+ 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
- @logger.warn "Error while fetching snapshot for client id '#{client_id}', resetting state: #{e.message}"
+ @logger.warn "Error while fetching state_data for client id '#{client_id}', resetting state: #{e.message}"
@logger.warn e
end
end
state_data ||= StateData.new
@@ -98,42 +134,42 @@
state_data
end
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
- def persist_snapshot(client_id, state_data)
+ def persist_state_data(client_id, state_data)
state_data.reset_elapsed_time
@storage.put(client_id, state_data.serialize)
end
def state_data_expired?(state_data)
- return unless state_data.timestamp.present?
+ return false unless state_data.last_interaction_timestamp.present?
state_data.elapsed_time > (Socrates.config.expired_timeout || 30.minutes)
end
- def instantiate_state(state_data, context)
- @state_factory.build(state_data: state_data, adapter: @adapter, context: context)
+ def instantiate_state(state_data, channel)
+ @state_factory.build(state_data: state_data, adapter: @adapter, channel: channel)
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 == State::END_OF_CONVERSATION
end
- def handle_action_error(e, client_id, state, context)
+ def handle_action_error(e, client_id, state, channel)
@logger.warn "Error while processing action #{state.data.state_id}/#{state.data.state_action}: #{e.message}"
@logger.warn e
- @adapter.send_message(@error_message, context: context)
+ @adapter.send_message(@error_message, channel)
state.data.clear
state.data.state_id = nil
state.data.state_action = nil
- persist_snapshot(client_id, state.data)
+ persist_state_data(client_id, state.data)
end
end
end
end