Skip to content

Commit

Permalink
Add reply processor to handle the SMS responses
Browse files Browse the repository at this point in the history
  • Loading branch information
acamino committed Apr 4, 2016
1 parent 9cc7483 commit 482ddaa
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 50 deletions.
11 changes: 1 addition & 10 deletions app/controllers/surveys_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ def voice
end

def sms
survey = Survey.first
render xml: welcome_message_for_sms(survey)
render xml: SMS::ReplyProcessor.process(user_response, cookies)
end

private
Expand All @@ -21,12 +20,4 @@ def welcome_message_for_voice(survey)
r.Redirect question_path(survey.first_question.id)
end.to_xml
end

def welcome_message_for_sms(survey)
Twilio::TwiML::Response.new do |r|
r.Message do |msg|
msg.Body "Thank you for taking the #{survey.title} survey"
end
end.to_xml
end
end
59 changes: 59 additions & 0 deletions lib/sms/reply_processor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module SMS
class ReplyProcessor
def self.process(message, cookies)
new(message, cookies).process
end

def initialize(message, cookies)
@message = message
@tracked_question = TrackedQuestion.new(cookies)
end

def process
return process_initial_response if initial_response?
process_succ_response if tracked_question.present?
end

private

attr_reader :message, :tracked_question

def initial_response?
tracked_question.empty?
end

def process_initial_response
survey = Survey.first
first_question = survey.first_question
tracked_question.store_or_destroy(first_question)
CreateResponse.for(first_question)
end

def process_succ_response
previous_question = tracked_question.fetch
Answer.create(attributes_for_answer(previous_question))
next_question = FindNextQuestion.for(previous_question)
tracked_question.store_or_destroy(next_question)
CreateResponse.for(next_question)
end

def attributes_for_answer(question)
{
question_id: question.id,
content: message,
source: Answer.sources.fetch(:sms),
from: 'from'
}
end

# Think about this case.
def welcome_message_o
survey = Survey.first
Twilio::TwiML::Response.new do |r|
r.Message do |msg|
msg.Body "Thank you for taking the #{survey.title} survey"
end
end.to_xml
end
end
end
25 changes: 18 additions & 7 deletions lib/sms/tracked_question.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
module SMS
class TrackedQuestion
NoQuestion = Class.new

def initialize(cookies)
@cookies = cookies
end

def store(question_id)
cookies[:question_id] = question_id
def store_or_destroy(question)
if question == Question::NoQuestion
destroy
else
cookies[:question] = serialize(question)
end
end

def fetch
cookies.fetch(:question_id, NoQuestion)
question = cookies.fetch(:question)
deserialize(question)
end

def destroy
cookies[:question_id] = nil
cookies[:question] = nil
end

def empty?
cookies[:question_id].nil?
cookies[:question].nil?
end

def present?
Expand All @@ -29,5 +32,13 @@ def present?
private

attr_reader :cookies

def serialize(question)
question.serializable_hash.to_yaml
end

def deserialize(question)
Question.new(YAML.load(question))
end
end
end
11 changes: 7 additions & 4 deletions spec/controllers/surveys_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
let!(:first_question) { create(:question, survey: survey, body: 'first') }
let!(:last_question) { create(:question, survey: survey, body: 'last') }

context 'when the first message arrives' do
before { post :sms }
context 'when the user replies to a question' do
before do
request.cookies[:question] = first_question.serializable_hash.to_yaml
post :sms, Body: 'yes'
end

it 'responds with a welcome message' do
it 'responds with the next question' do
expect(content_for('/Response/Message/Body'))
.to eq('Thank you for taking the bees survey')
.to eq("last\n\nReply to this message with your answer")
end
end

Expand Down
75 changes: 75 additions & 0 deletions spec/lib/sms/reply_processor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'rails_helper'

describe SMS::ReplyProcessor do
describe '.process' do
subject { described_class.process(message, cookies) }

let(:survey) { create(:survey, title: 'bees') }

let!(:first_question) { create(:question, survey: survey, body: 'first') }
let!(:last_question) { create(:question, survey: survey, body: 'last') }

context 'when there are no tracked questions' do
let(:message) { 'start' }
let(:cookies) { {} }

it 'responds with the first question' do
expect(content_for('/Response/Message/Body'))
.to eq("first\n\nReply to this message with your answer")
end

it 'track the current question' do
subject
expect(cookies[:question]).to include(first_question.id.to_s)
end
end

context 'when there are a tracked question' do
let(:message) { 'answer for the question' }
let(:cookies) { { question: serialize_question(first_question) } }

it 'creates an answer' do
expect do
subject
end.to change { Answer.count }.by(1)
end

context 'when there are questions available' do
it 'responds with the next available question' do
expect(content_for('/Response/Message/Body'))
.to eq("last\n\nReply to this message with your answer")
end

it 'track the current question' do
subject
expect(cookies[:question]).to include(last_question.id.to_s)
end
end

context 'when there are no more questions available' do
let(:cookies) { { question: serialize_question(last_question) } }

it 'responds with the exit message' do
expect(content_for('/Response/Message/Body'))
.to eq('Thanks for your time. Good bye')
end

it 'untrack the current question' do
subject
expect(cookies[:question]).to be_nil
end
end
end
end

private

def content_for(xpath)
document = Nokogiri::XML(subject)
document.at_xpath(xpath).content
end

def serialize_question(question)
question.serializable_hash.to_yaml
end
end
63 changes: 34 additions & 29 deletions spec/lib/sms/tracked_question_spec.rb
Original file line number Diff line number Diff line change
@@ -1,68 +1,73 @@
require_relative '../../../lib/sms/tracked_question'
# require_relative '../../../lib/sms/tracked_question'
require 'rails_helper'

describe SMS::TrackedQuestion do
subject(:current_question) { described_class.new(cookies) }
subject(:tracked_question) { described_class.new(cookies) }

describe '#store' do
let!(:question) { build_stubbed(:question) }
let!(:serialized_question) { question.serializable_hash.to_yaml }

describe '#store_or_destroy' do
let(:cookies) { {} }

it 'stores the current question id' do
current_question.store('1')
expect(cookies[:question_id]).to eq('1')
it 'stores the question' do
tracked_question.store_or_destroy(question)
expect(cookies[:question]).to eq(serialized_question)
end
end

describe '#fetch' do
context 'when there are a current question id' do
let(:cookies) { { question_id: '1'} }
context 'when the given question is Question::NoQuestion' do
let(:cookies) { { question: serialized_question } }

it 'returns the current question id' do
expect(current_question.fetch).to eq('1')
it 'destroys the question' do
subject.store_or_destroy(Question::NoQuestion)
expect(cookies[:question]).to be_nil
end
end
end

context 'when there are no current question' do
let(:cookies) { {} }
describe '#fetch' do
context 'when there are a tracked question' do
let(:cookies) { { question: serialized_question } }

it 'returns the SMS::CurrentQuestion::NoQuestion' do
expect(current_question.fetch).to eq(SMS::TrackedQuestion::NoQuestion)
it 'returns the question' do
expect(tracked_question.fetch).to eq(question)
end
end
end

describe '#destroy' do
let(:cookies) { { question_id: '1'} }
let(:cookies) { { question: serialized_question } }

it 'destroys the current question id' do
it 'destroys the question' do
subject.destroy
expect(cookies[:question_id]).to be_nil
expect(cookies[:question]).to be_nil
end
end

describe '#empty?' do
context 'when there are no current question' do
context 'when there is no tracked question' do
let(:cookies) { {} }

it 'returns false' do
expect(current_question.empty?).to be_truthy
it 'returns true' do
expect(tracked_question.empty?).to be_truthy
end
end

context 'when there are a current question id' do
let(:cookies) { { question_id: '1'} }
context 'when there is a tracked question' do
let(:cookies) { { question: serialized_question } }

it 'returns true' do
expect(current_question.empty?).to be_falsey
it 'returns false' do
expect(tracked_question.empty?).to be_falsey
end
end
end

describe '#present?' do
context 'when there are a current question id' do
let(:cookies) { { question_id: '1'} }
context 'when there is a tracked question' do
let(:cookies) { { question: serialized_question } }

it 'returns true' do
expect(current_question.present?).to be_truthy
expect(tracked_question.present?).to be_truthy
end
end
end
Expand Down

0 comments on commit 482ddaa

Please sign in to comment.