state machine is saying that my state is invalid

326 views
Skip to first unread message

Jeffrey Katz

unread,
Dec 11, 2012, 2:08:08 PM12/11/12
to pluginaw...@googlegroups.com
I am using state_machine in combination with zendesk's call_center gem (https://github.com/zendesk/call_center) for a twilio application.  For some reason, I am now getting the following error message: ActiveRecord::RecordInvalid (Validation failed: State is invalid).

When I simply do c = Call.new c.valid? I am told that it is invalid, and then that the "state is invalid" when I print the error messages.

I am copying my code below (my apologies for the length).  Or you can view in this gist https://gist.github.com/4261123

Thanks for the help!

class Call < ActiveRecord::Base
  include CallCenter

  belongs_to :user

  attr_accessible :to, :from, :called, :caller
  attr_accessible :account_sid, :call_sid, :call_status, :digits

  call_flow :state, :initial => :initial do

    state :initial do
     event :incoming_call, :to => :greeting
    end

    state :greeting do
      # TODO if user has a current question id, transition to play_question
      # TODO else transition to play_lecture
      event :greeted, :to => :advance_user
      event :no_classroom, :to => :gather_classroom_number

      response do |x|
        if user.classroom_id == nil
          x.Say "It looks like you are not assigned to a classroom.  Let's take
          care of that now."
          x.Redirect flow_url(:no_classroom)
        else  
          x.Say "Welcome back! Let's get back to your classes."
          x.Redirect flow_url(:greeted)
        end
      end
    end

    state :gather_classroom_number do
      event :received_number, :to => :evaluate_classroom_number

      response do |x|
        x.Gather :numDigits => '3', :action => flow_url(:received_number) do
          x.Say "Please enter the 3 digit classroom number given by your teacher.
          If you do not know this number, press 999 to move on."
        end
      end
    end

    state :evaluate_classroom_number do
      event :no_number, :to => :advance_user
      event :correct_number, :to => :advance_user
      event :wrong_number, :to => :gather_classroom_number
      response do |x|
        if digits == '999'
          x.Say "Ok, we'll move on."
          x.Redirect flow_url(:no_number)
        elsif user.assign_classroom == nil
          x.Say "Looks like you entered the wrong classroom number.
          Let's try it again."
          x.Redirect flow_url(:wrong_number)
        else 
          user.assign_classroom
          user.save
            x.Say "Great you are now in the classroom #{user.classroom.name} which is taught by #{user.classroom.teacher.name}"
            x.Redirect flow_url(:correct_number)
        end
      end
    end

    state :determine_current_segment do
      event :going_to_lecture,  :to => :play_lecture
      event :going_to_question, :to => :play_question
      
        response do |x|
         # x.Say "Determining current segment."
          if user.on_lecture?
            #x.Say "Going to lecture #{user.lecture.id}"
            x.Redirect flow_url(:going_to_lecture)
          else
            # x.Say "Going to question #{user.question.id}"
            x.Redirect flow_url(:going_to_question)
          end
       end
    end

    state :play_lecture do
      
      event :lecture_finished, :to => :repeat_or_advance

      response do |x|
        x.Gather :numDigits => '1', :action => flow_url(:lecture_finished) do
          x.Play user.lecture.soundfile.url
          user_lecture = user.user_lectures.build(:lecture_id => user.lecture.id)
          user_lecture.save
        end
        #HOLD_MUSIC.sort_by { rand }.each do |url|
        #  x.Play url
        #end
      end
    end

    # In this state, we should have access to digits
    state :repeat_or_advance do
      event :request_repeat, :to => :determine_current_segment
      event :request_advance,  :to => :advance_user
      response do |x|
          #x.Say "You pressed #{digits}."
         if digits == "1"
            x.Say "Ok, we'll repeat this for you."
            x.Redirect flow_url(:request_repeat)
         else
            x.Redirect flow_url(:request_advance)
         end
      end
    end

    state :play_question do
      event :submit_answer, :to => :check_if_correct
        response do |x|
          x.Gather :numDigits => '1', :action => flow_url(:submit_answer) do
            x.Play user.question.soundfile.url
          end
        end
    end

    state :check_if_correct do
      event :answer_correct,   :to => :advance_user
      event :answer_incorrect, :to => :question_explanation
        response do |x|
          x.Say "You pressed #{digits}."
          answer = user.user_answers.build(question_id: user.question.id, value: digits, user_lecture_id: user.user_lectures.last.id)
          answer.assert_correct
          answer.save
            if digits == user.question.answer.to_s #you will need to write a function that checks if its correct or not
              x.Say "Great, that's right.  Now we'll move onto the next question."
              x.Redirect flow_url(:answer_correct) #then send to next question, perhaps by 
              #first writing a state that changes current question to the next question.  Then resend to current question.
            else
              x.Redirect flow_url(:answer_incorrect)
            end
        end
    end

    state :question_explanation do
      event :explanation_end, :to => :advance_user
        response do |x|
          x.Play user.question.explanationfile.url
          x.Redirect flow_url(:explanation_end)
        end
    end

     state :advance_user do
        event :advancing_user, :to => :determine_current_segment
        response do |x|
          user.advance
          user.save #don't know why this is not working
          # x.Say "We have advanced you to the next part."
          x.Redirect flow_url(:advancing_user)  
        end
     end



    #write logic for 
      #if question is correct, and 
        # if there is another question, 
            # go to the next question
        # if there is not another question
          # go to congrats, 
            #and do you want to go to another lecture
      # if question is incorrect, play explanation
        # then play next question
      # end



    state :ended do
      after(:success) do
        update_attributes(:ended_at => Time.now)
        #user.update_attributes(:lecture_id => user.lecture.id, :question_id => user.question.id)
      end
    end


    state any do
      event :hang_up, :to => :ended
    end
  end

  # ===============
  # = Call Center =
  # ===============

  def run(event)
    send(event)
    render
  end

  def flow_url(event)
    params = {
      :call_id => self.id,
      :event => event.to_s
    }

    uri = URI.join(ENV['TWILIO_TUNNEL_URL'], "calls/flow")
    uri.query = params.to_query
    uri.to_s
  end

  def redirect_to(event, *args)
    account.calls.get(self.call_sid).update({:url => flow_url(event)})
  end

  def wait_time
    Time.now - (self.waiting_at || self.created_at)
  end

  # ========
  # = REST =
  # ========

  def account
    client.account
  end

  def client
    self.class.client
  end

  def self.client
    @client ||= Twilio::REST::Client.new ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']
  end

  # ===========
  # = Routing =
  # ===========

  MAX_WAIT_TIME = 60
  HOLD_MUSIC = [
  ].freeze

  serialize :conference_history, Array

  def can_connect?(call)
    !conference_history.include?(call.from)
  end

  def call_conference_name(call)
    [self.call_sid, call.call_sid].sort.join('::')
  end

  def connect(call)
    self.conference_name = call_conference_name(call)
    Rails.logger.info "Creating conference: #{self.conference_name}"
    self.conference_history << call.from
    self.save!
    self.redirect_and_put_in_conference!
  end

  def location
    if caller_states = Carmen::states(self.caller_country)
      title_and_abbreviation = caller_states.detect { |title, abbr| abbr == self.caller_state }
      if title_and_abbreviation
        return "Someone in #{self.caller_city.capitalize}, #{title_and_abbreviation.first}"
      end
    end
    "Someone in #{self.caller_city.capitalize}, #{self.caller_state}"
  rescue Carmen::StatesNotSupported, Carmen::NonexistentCountry
    "An Unknown Caller"
  end

  def as_json(options = {})
    {
      :location => location,
      :conference_name => conference_name
    }
  end

end

Reply all
Reply to author
Forward
0 new messages