When JSON encoding does not play well with character encoding

There comes a time for a developer to face a bug so weird that it leaves him/her speechless and scramble to find an answer wherever possible.

After seemingly innocent deployment to production, I noticed huge spike in errors like the following:

ActionView::Template::Error: "\xEB" from ASCII-8BIT to UTF-8

Offending line was JSON encoding of geo location information provided in request header by our cache provider. \xEB happens to be ß character in ASCII, so it was probably some German city name.

Our site’s character type is UTF-8, and Ruby 2.0’s default encoding is UTF-8. I also verified that Encoding.default_internal and Encoding.default_external are both UTF-8. It was our cache provider inserting non-UTF-8 character in request header.

No problem. I will just force UTF-8 encoding on the string.

Then, I was faced with another kind of problems.

ActionView::Template::Error: partial character in source, but hit end

Something is off.

Off to the best debugging method in Ruby – print the string encoding. And it’s ISO-8859-1 (or Latin-1).

So, the final solution was to force ISO-8859-1 encoding and encode it again in UTF-8.

string.to_s.force_encoding("ISO-8859-1").encode("UTF-8")

However, I am not 100% happy with the solution. It just smells. It works for now, but I need to look for better solution when I get a chance.

For bonus, check this out – The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

Post to Twitter

MongoDB Query Performance Gotchas

At my current job, we use mongodb as our main database and as you can imagine we query our database a lot. The followings are what I found while investigating some slow performance. Hopefully it will prevent you from making the same mistake or pinpoint the source of problem quickly. We are using Rails 4 with Mongoid.

1. Don’t use regular expression with /i in queries.

One of the offending query looked pretty innocent. Customer is embedded document with email attribute and email field is indexed.

def self.with_email(email)
  self.where("customer.email" => /\Aemail\z/i).count
end

When I ran Benchmark.bmbm where the method was repeated 100 times, it returned the following result.

       user     system      total        real
   0.090000   0.010000   0.100000 ( 27.656723)

Compare that with when you call the same without regexp.

def self.with_email(email)
  self.where("customer.email" => email).count
end

Performance result is day and night.

       user     system      total        real
   0.080000   0.000000   0.080000 (  0.122888)

So, why the difference? I couldn’t quite figure it out myself. I knew that Mongodb uses B-Tree for index and index for email was already created. What about the regular expression was making it slow? I thought perhaps the anchor tags (\A and \z) were causing it, but that wasn’t it. I tried using substring of email and anchoring the beginning of email string (using ^), but that didn’t yield difference either.

I finally turned to Stack Overflow for assistance, and within a few minutes, someone provided exact answer I was looking for. They key to the answer was in output of explain method.

Look at the nscanned and indexBounds in the output of explain method for the first method. It scanned all index because the upper and lower bounds are not defined ("" and {}).

{
                     "cursor" => "BtreeCursor customer.email_1",
                 "isMultiKey" => false,
                          "n" => 781,
            "nscannedObjects" => 781,
                   "nscanned" => 500000,
    "nscannedObjectsAllPlans" => 781,
           "nscannedAllPlans" => 500000,
               "scanAndOrder" => false,
                  "indexOnly" => false,
                    "nYields" => 1397,
                "nChunkSkips" => 0,
                     "millis" => 406,
            "indexBounds" => {
    "customer.email" => [
        [0] [
            [0] "",
            [1] {}
        ],
        [1] [
            [0] /test/i,
            [1] /test/i
        ]
    ]
  }
}

Compare that to the output of explain method for the second method. Exactly the same upper and lower bounds, and it only scanned extremely small number of objects. This explains the big performance difference between the two methods.

{
                     "cursor" => "BtreeCursor customer.email_1",
                 "isMultiKey" => false,
                          "n" => 230,
            "nscannedObjects" => 230,
                   "nscanned" => 230,
    "nscannedObjectsAllPlans" => 230,
           "nscannedAllPlans" => 230,
               "scanAndOrder" => false,
                  "indexOnly" => false,
                    "nYields" => 1,
                "nChunkSkips" => 0,
                     "millis" => 0,
            "indexBounds" => {
    "customer.email" => [
        [0] [
            [0] "test@email.com",
            [1] "test@email.com"
        ]
    ]
  }
}

2. Don’t convert collection to array

Another slow performance was due to the following line of code. ProductA collection had about 20 documents and ProductB collection had about 2 documents. Another seemingly innocent code, but why?

products = ProductA.all + ProductB.all

Back to measuring performance with Benchmark (this time I just did it with Benchmark.bm).

Benchmark.bm do |x|
  x.report {n.times {|n| products = ProductA.all + ProductB.all}}
end
       user     system      total        real
  99.810000   2.630000 102.440000 (114.282440)

Let’s confirm the performance of retrieval.

Benchmark.bm do |x|
  x.report {n.times {|n| ProductA.all.count}}
end
       user     system      total        real
   0.430000   0.000000   0.430000 (  0.437339)

Benchmark.bm do |x|
  x.report {n.times {|n| ProductB.all.count}}
end
       user     system      total        real
   0.440000   0.000000   0.440000 (  0.456449)

That’s pretty reasonable performance. So, it must be something in the addition of two collections. When you add two collections using Mongoid, it returns an array, so it might be something related to converting to an array. Let’s check the performance on that.

Benchmark.bm do |x|
  x.report {n.times {|n| ProductA.all.to_a}}
end
       user     system      total        real
 110.070000   1.640000 111.710000 (117.081572)

Ah-ha! Looks like we found the problem. Converting to an array is quite slow. In order to use single collection, without converting to an array, I created a parent class – ProductParent, and ProductA and ProductB would inherit from that class. In such way, one collection is used and subclasses are distinguished by just type field. Actually, it should have been parent/subclass structure since ProductA and ProductB are kinds of a Product, and both share many of the same attributes.

After the re-arrangement, the following is the performance.

Benchmark.bm do |x|
  x.report {n.times {|n| ProductParent.all.count}}
end
       user     system      total        real
   0.480000   0.010000   0.490000 (  0.490489)

What a difference it made!

Finding these two performance gotchas have been a fun journey, and I hope they are useful to you. Do you have any gotchas you have found? Let me know, and I will post a link here!

Post to Twitter

Practice programming like you practice martial arts

I have been taking a zen meditation class, which consists of one hour of meditation and one hour of buddhist teaching. I haven’t felt all the physiological benefits of meditation yet – I struggle to stay awake during meditation, but the buddhist teaching has been pretty interesting.

In one class, he used a quote from Enter the dragon to explain how you should not take the buddhist teachings literally.

Don’t think. FEEL. It’s like a finger pointing at the moon. Do not
concentrate on the finger, or you will miss all of the heavenly glory.

It made me think of my journey to reach 2nd-degree black belt in TaeKwonDo. It took me about three years in Korea, and as opposed to many TaeKownDo schools in the US, it was pretty intensive. Everyday, we start with practicing the same kicks and punches – white belts to black belts. You can tell the difference between kicks from white belts and black belts, but where the difference is starkly obvious is during sparring. So, how are they different? Higher black belts can do without thinking. It’s almost their 2nd nature or sixth sense. Their response or counter attack is very quick that sometimes their counter kick/punch lands before the opponent. Even as a 2nd-degree black belt, I still had to think about what opponent does and how I would react. I believe it comes from constant practicing.

When I think about good developers I have witnessed/interacted with, they seem to see beyond surface of problems and are capable of coming up with adequate and correct solutions quickly. As I write more and more codes to solve more requirements and bugs, I began to recognize the pattern and past mistakes I have made which in turn has been enabling me to write better codes with less mistakes more quickly. I have been told that the best way to good at coding is by coding a lot and reading good codes. It’s probably not the case for all developers, but I believe the practice indeed make many developers better.

I’ve practiced a lot of coding at Hacker Dojo (Dojo means a training place) and Ruby Koans (paradoxical questions to reach sudden intuitive enlightenment) helped me a lot in learning Ruby. Their similarities with martial art practices are quite keen.

I will leave another quote of Bruce Lee, that’s also very relevant to coding.

Quote-Ten-Thousand-Kicks-Bruce-Lee

Post to Twitter

Importance of owning a market segment – Monopoly, market leader, and innovator’s dilemma

I am having a great time listening to lectures for Stanford CS183B – How to start a startup online. All lectures have been extremely good, and it allowed me to get a sense of how Y Combinator accepted companies into its batches and what made them successful. I am learning so many things, and I see some common underlying themes I want to write about, but two lectures really struck a chord with me.

Before the Startup by Paul Graham and Business Strategy and Monopoly Theory by Peter Thiel, in my opinion, were dead on. Many startups were able to thrive on because many of their ideas seemed bad at the time. If you have an obviously good idea, then many people will go for the same idea. That leads to competitions. For consumers, competition is good, but for business not so. And in a sense, overarching goal of a business is to become a monopoly in its market.

I find Peter and Paul’s main points similar to those of Crossing the Chasm and Innovator’s Dilemma.

In Crossing the Chasm, the author, Geoffrey Moore, argues that in order for disruptive technology company to survive, it needs to reach beyond technology pioneers and early adapters and move to early majority market. The gap between them is so great that many technology companies fail, and he named it “chasm”. What’s the strategy for crossing the chasm successfully? He coined the first market segment in early majority “beach-head market”. This is where company has to put all its resources and try to conquer. Company can get it wrong, of course, and it’s imperative that company finds this sooner than later so that it can correct its course, and hopefully new one is the right one.

One thing he emphasizes in conquering the beach-head market is to be the market leader. Who is market leader? I believe it’s synonymous to monopoly. You need to have at least 60% of the market share in the segment. Market leader has several advantages over #2 and #3 in the market. Because of its reach in the market, there will be auxiliary companies supporting the market leader, thus creating the whole eco-system. Often times, cost of changing product from leader to another is so great that it gives the leader leeway with its customers. After taking the leadership, it can expand to adjacent markets with money and time it earned being the market leader of beach-head market (he compared to going to adjacent markets as using the top pin in bowling to knock down other pins). If I remember correctly, Geoffrey Moore suggested the beach-head market be adequate enough for a startup to tackle.

Similarly, in Innovator’s Dilemma, the author, Clayton Christensen provides examples after examples, time and time again, a startup seems to come out of nowhere and take over the 900-lbs. gorilla of the market. What’s interesting is that companies in Clayton’s examples represent diverse markets – consumer, enterprise, and even to steel mills. They all had a few things in common.

They all started by attacking the market segment with much lower margin so market leader is happy to let them take it. Market leader is usually larger in size, and thus require much higher margin to operate and make profits. While market leader grows nominally by producing products for handful of larger customers, startup becomes leader in its segment and advances its products at faster rate. When the market leader notices startups’ progress, it’s often too late and even its larger customers are ready to become startup’s customers.

Those two books definitely shaped my views of startups, and Paul and Peter’s lectures seem to validate the books’ main points. There are, of course, many other things that contribute to a startup’s success or failure – co-founder dynamics, team, culture, executions, and any external events. However, I believe, one thing is sure to attribute to startup’s success.

It could be that it’s a market segment on one cares or that the idea you are working on seems to be bad to everyone else. Whatever it is, startup needs to become a leader in market segment to survive and succeed.

Become a leader or monopoly in a market segment

Post to Twitter

How to create a (simple) watch app on Pebble

It’s been a couple of months since I joined Pebble as a full-stack web developer. Most of people know Pebble by its phenomenal success in its Kickstarter campaign – raising more than $10M. Pebble is also known as the company that started the smartwatch/wearable market.

Ever since I started, I have come to like a few watch apps and faces, and I’ve always wanted to create something on my own (since I like to build things). Even though we have a hack day every other week, I wasn’t able to participate because of things that needed to get done.  But today was a special one. It was hack night, instead of hack day, and I was able to participate.

In today’s hackathon, I built a simple app that displays a short quote like Unix fortune using Pebble’s pebblejs and CloudPebble. It was super easy. Almost too easy. I don’t interact with our dev evangelist team much, I have new respect for those guys. They worked hard to make it easy for developers to develop apps on Pebble.

I have shared my code on my github account.  You should check it out, and it should be self-explanatory. Use pebblejs as reference to look up functions.

https://github.com/yangtheman/fortunes_pebble_app

Here we go.

1. Enable Developer Mode on your mobile phone. On iPhone: Settings > Pebble > Developer Mode > On

IMG_3529

IMG_3527

2. Enable Developer Mode on Pebble mobile app. On iPhone: Pebble App > Settings (where you see My Pebble) > Developer > Enabled

IMG_3528

IMG_3530

3. Log in to CloudPebble using the same username and password as one you created when you first set up Pebble.

4. Create a new project and select Pebble.js as Project Type.

Screen Shot 2014-07-17 at 10.02.49 PM

5. Click on app.js on the left column.

Screen Shot 2014-07-17 at 9.32.22 PM

6. If you want, you can leave the default code as it is. Or you can copy and paste my code in pebble-js-app.js. Don’t publish your app using my code as it is, but you are certainly welcome to try it out and tweak it!

7. When you want to see the app run on the watch, click on the white Play button with green background on the right side of the screen. This should automatically compile and install the app on your watch. Make sure you have an empty slot in the locker on Pebble mobile app. If compile/install fails, toggle developer mode on Pebble mobile app as in step 2 above and try again. If you have any debug statements using console.log, you will see the log messages in ‘View Logs’ as you interact with the app.

Screen Shot 2014-07-17 at 9.32.12 PM

8. You can change your code and compile/run until you are satisfied. Next steps are for publishing the app on Pebble App Store.

9. Get some screenshots from your watch app. From CloudPebble, click on COMPILATION link. Then click on SCREENSHOT button. It will take screenshot of what’s on the watch. Take a couple of different screenshots.

Screen Shot 2014-07-17 at 9.41.50 PM

10. Get the PBW file. In COMPILATION view, you will see a list of builds. You probably want to grab the latest one (assuming the last build is the one you want to publish).

11. Log in to Pebble Dev Portal using the same username and password as one you created when you first set up Pebble, and click on Publish a Pebble App button.

10. In our case, we are adding a watch app, so click on Add a Watchapp link on the left.

Screen Shot 2014-07-17 at 10.09.01 PM

12. Filling out the form should be pretty straight forward.

12.1. For icons, make sure they are Black with White background. Do not use transparent background. 

12.2. Use the screenshots you downloaded earlier for screenshots.

12.3. You can be as creative as your want for the header image.

13. Once you are done, hit CREATE button at the bottom of the page.

14. You are not quite done yet. Lastly, go down to Releases section and publish your first version. 

Screen Shot 2014-07-17 at 10.11.01 PM

15. After that you can make your app public by clicking on the Make Public button on the top right corner of the page.

16. Ta-Da! Your app is ready on Android platform first. It will take a day or two to appear on iOS platform. There you go. You now successfully created and published the first app!

Post to Twitter

User-friendly 500 and 404 pages on Rails 3

This came in handy for me, so I wanted to share this particular way of handling 404 and 500 errors.

First, rescue errors and tell what to do in application_controller.rb.

  if Rails.env.production?
    unless Rails.application.config.consider_all_requests_local
      rescue_from Exception, with: :render_500
      rescue_from ActionController::RoutingError, with: :render_404
      rescue_from ActionController::UnknownController, with: :render_404
      rescue_from ActionController::UnknownAction, with: :render_404
      rescue_from ActiveRecord::RecordNotFound, with: :render_404
    end
  end

In the same application_controller.rb, then you create the methods specified above.

  def render_404(exception)
    @not_found_path = exception.message
    respond_to do |format|
      format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
      format.all { render nothing: true, status: 404 }
    end
  end

  def render_500(exception)
    logger.info exception.backtrace.join("\n")
    respond_to do |format|
      format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
      format.all { render nothing: true, status: 500}
    end
  end

Since it’s using the application layout, you will see the error message you specify in the layout. The following is my 500 error page, /views/errors/internal_server_error.html.haml. For 404 page, you can also use @not_found_path instance variable in the view as well.

#errors-page
  .thumbnail.errors
    .errors-500
    .caption
      %h5 Sorry
  %h1 500 Internal Server Error

  - # Do not remove this line. It is used for development
  - # purposes. When an error is found. In development, it
  - # shows the logs, in production it is nil.
  .error-msg
    = @log

Cheers,

Post to Twitter

Best way to learn is to imitate (and how it should apply to startups in emerging markets)

Recently there were interesting articles beng circulated and discussed in the valley. One is Black Swan Farming by Paul Graham and the other one is Screw the Black Swans by Dave McClure. Interestingly enough, the fireside chat with Vinod Khosla at the TechCrunch Disrupt SF 2012 was a timely interview and speaks to the different viewpoints.

I suspected, but was surprised to learn that out of all YC companies, Airbnb and Dropbox account for three quarters (3/4) in terms of about $10 Billion valuation. Paul was saying how difficult it is to pick a game changer winner. What makes it more difficult is how great ideas seem like bad ideas in the beginning. If an idea looks good, then everyone including large companies will work on it, and startups will have even less chance of succeeding. It’s when an idea seems bad and thus hard time getting funded, but at the end succeeds, it’s a game changer (homerun).

Vinod Khosla said pretty much the same. His fund looks for companies that will make tremendous impact when successful. It doesn’t matter when they fail. Founders will move on to a new project. But they don’t want to invest in companies that will make a little to no impact when successful. Again, Vinod is looking for a game changer (homerun) company.

Dave on the other hand is saying that 500 startups focus on Ichiro’s of the world (consistent hitting) rather than Barry Bond’s of the world (homerun king). He goes further into discussing the differences between YC and 500 startups (like hackers vs. hustlers), but my main takeaway was what kind of companies they were looking to fund.

These discussions made me think about how startups in emerging markets (including Korea) could do better. When babies are born, they learn mostly by imitating people around them. There is also an old saying, “Imitation is the best form of flattery”. Many amateur athletes also learn by imitating professional players (watching and learning). Korea is the only country I know best outside the US, and I always noticed the lack of virtuous seed funding cycle in Korea – successful founders seed investing in other startups. Once startup is up and running, and shows notable tractions, they can raise money from VCs and the process and valuation will be much better. However, startups often fail even before they could show traction because either they make multiple mistakes or run out of money before they can pivot. Without proper seed funding (and mentoring), they will have really hard time reaching the traction point.

At any rate, I think the best way for startups in Korea (or in emerging markets) to produce good enough winners to start the virtuous cycle is to imitate successful US startups in Korea (or in emerging markets), and after having a few successful exits, the founders can help other founders by investing and mentoring them. Furthermore, after successful exits, those founders would be in much better position to take bigger risk and attempt to make big impacts. In baseball analogy, it would be something along the line of having a few good seasons of hitting consistently, then you can swing for homeruns. No doubt those good seasons would be a confidence builder as well.

I keep hearing how there are many incubators and accelerators popping up in Korea. Someone recently expressed a concern that it’s like a startup bubble and a few failures will have devastating effect on the whole startup movements. I think this type of imitation strategy will be good at least in the beginning. After there are good number of successful and experienced founders, they would be better equipped to make sounding decisions and also to help other up-and-coming founders. It would truly strengthen startup community in Korea (or in emerging markets).

Post to Twitter

Install ImageMagick on Lion from source

I’ve had a lot of problems trying to install ImageMagick using brew. Use the following series of commands to install from source. Got this from Stackoverflow answer.

cd /tmp
curl -OL ftp://ftp.imagemagick.org/pub/ImageMagick/ImageMagick.tar.gz
tar -xzf ImageMagick.tar.gz
cd ImageMagick-[VERSION]/
./configure --prefix=/usr/local --disable-static --with-modules --without-perl --without-magick-plus-plus --with-quantum-depth=8 --disable-openmp --with-gs-font-dir=/usr/local/share/ghostscript/fonts
make
sudo make install

Post to Twitter

How can you not get romantic about startups?

Recently I watched Moneyball – a non-fiction movie about how Billy Beane at Oakland A’s used statistics to get most under-valued players and create the most valued (least amount spent per game) team in MLB. If you haven’t watched it, I strongly recommend it. I don’t usually shed tears watching a movie, and as a proud male (not that I am a sexist), I wouldn’t admit it even if I did, but I did watching Moneyball. Moneyball! It wasn’t a Oscar-worth drama. Nor was it gut-wrenching tear jerker. But why did I cry watching the movie?

*SPOILER ALERT!*

Because it was about one man’s lonely, uncertain fight against status quo under heavy pressure and criticism. Once a promising baseball player, Billy turned down a full scholarship to Stanford to join New York Mets as first-round draft for $125K. But he never became a top baseball player his scouts had predicted. He went through a number of minor leagues and other major league teams with not much to show for. He ended up at Oakland A’s, and he asked to become a scout. Later he became an assistant GM and a GM.

His push to use statistical analysis to pick players was controversial to say the least. He picked players against professional scouts’ advice. The scene where he and another coach visiting Scott Hatteberg was touching, since you could tell from his house and his reaction afterwards, he probably thought he could never play baseball again, but he was so grateful for having been given another chance. First 40 or so games, Oakland A was dead last in its division. Pressure and criticism were directed to him from all directions. It looked like he was divorced and had a daughter who was staying with his ex-wife. He tried to hide the pain he was going through, but the daughter could still see everything was not quite alright.

Then, the team started winning and became the first in its division. It had 19-game winning streak. On the game where they could have 20-game winning streak, they blew 11-0 lead at 11-11 tie. Then, Scott Hatteberg, the guy all baseball teams had written off as too injured to play, stepped up and hit a walk-off home run. They achieved 20-game winning streak.

What’s more touching is when Billy turned down Red Sox’s $12.5M offer to stay with Oakland A’s because his decision to go with New York Mets was his last decision made based on money.

Also, another notable thing he said is “How can you not get romantic about baseball?” after some dramatic finish.

It may have to do with my personality – always rooting for underdogs, preferring hole-in-the-wall or local places to established places, etc., but the whole movie was touching to me because of its resemblance to startups. Not all startups are made up of under-valued coders/marketers, but I believe some are. Somewhere in a corporate world, there would be some who think their skills and opinions aren’t properly appreciated (not financially). I wouldn’t call entrepreneurs misfits, but they are definitely different. They know the odds of success is low (1 out of 10 or less), but they keep going against all odds, criticism, roller coaster emotions, and tremendous pressure. Most of them are in it to challenge status-quo and traditional way of doing things, and to disrupt the market. Often times, they are going against huge corporations with lots of cash. When one makes it, it’s a home run (but not always). Sometimes they will turn down a big offer to keep moving ahead because they believe in the products, company and the team. Many startups, whether they make it or not, have dramatic stories behind them.

Seriously, How can you not get romantic about startups?

Post to Twitter

Simple Ruby on Rails app using Twilio API

Twilio created cloud telephony services made mainly for developers. I’ve been meaning to figure out how to use their service, and I had a chance to develop a Ruby on Rails app using Twilio app called Phonein. You can check out the source code here. It’s nothing fancy, and totally not optimized. I also used Twitter’s Bootstrap CSS/JS package.

While Twilio had a few samples for Ruby on Rails, it was kind of outdated and a few things were incorrect. I wanted to share what I did to use Twilio API to help you come up to speed quickly.

Basically my app is for home-care professionals, and it allows them to check in and out using client’s phone. For Twilio, the followings are its requirements.

  • Identify client by phone number
  • Identify home-care professional by 6-digit identification code
  • Check in the home-care professional at the client’s location
  • Read out tasks for the home-care professional
  • Check out the home-care professional from the client’s location

Twilio has its own markup language called TwiML, and in Twilio controller I created, TwiML is used to create views (or voices). I also created certain POST actions under Twilio controller that will receive input from phone (digits punched in by a person) and use it to look up information.

1. Answer incoming call

When you sign up with Twilio, you can configure destination URL Twilio will invoke when it receives an incoming call. For mine, it would be “http://phonein.herokuapp.com/twilios/incoming”. So, I have “incoming” action defined in “Twilios” controller.

Controller in app/controller/twilios_controller.rb

Just like a regular Ruby on Rails app, controller gets objects ready for the view. It looks up client using the incoming phone number. If client is not found, it says (instead of displaying) an error message and hangs up. If client is found, it asks user to enter a 6-digit code. @post_to object contains URL that will be invoked after user interacts with Twilio. In our case, it’s after user enters code.

  
class TwiliosController < ApplicationController      
  BASE_URL = "http://phonein.herokuapp.com/twilios"         

  def incoming          
    # Get client by phone number          
    client_phone = params['From'][2..params['From'].size]          
    @client = Client.find_by_phone(client_phone)               
    
    if @client.nil?               
      render :action => "no_client.xml.builder"
    else
      @post_to = BASE_URL + "/verify?client_id=#{@client.id}"
      render :action => "incoming.xml.builder", :layout => false
    end
  end
end

View in app/views/twilios/incoming.xml.builder

xml.instruct!
xml.Response do
  xml.Gather(:action => @post_to, :numDigits => 6) do
    xml.Say "Welcome to #{@client.name}'s residence. Please enter your 6 digit code."
  end
end

View in app/views/twilios/no_client.xml.builder

xml.instruct!
xml.Response do
  xml.Say "Client could not be found."
  xml.Hangup
end

2. Verifies home-care professional, checks in, and reads tasks.

Next, look up the home-care professional by the 6-digit code. The numbers user punched will be in ‘Digits’ parameter. If user is found but has not checked in before, it checks in the user and gives some options. If user has already checked in before, user is presented with a few options including an option to check out.

Controller in app/controller/twilios_controller.rb

In addition to incoming action.

class TwiliosController < ApplicationController   

  def verify     
    @client = Client.find(params[:client_id])         
    @agent = Agent.find_by_code(params['Digits'])          
    if @agent.nil?       
      # If no agent is found, say "no agent found" error message and hang up.       
      @post_to = BASE_URL + "/verify?client_id=#{@client.id}"       
      render :action => "no_agent.xml.builder"
      return
    else
      if @agent.checked_in?(@client.id) 
        @message = "You have already checked in."
      else
        @agent.check_in(@client.id)
        @message = "Now you are checked in."
      end
    end 

    # Default action is direction
    @post_to = BASE_URL + "/direction?agent_id=#{@agent.id}&client_id=#{@client.id}"
    render :action => "direction.xml.builder", :layout => false
  end
end

View in app/views/twilios/direction.xml.builder

xml.instruct!
xml.Response do
  xml.Gather(:action => @post_to, :numDigits => 1) do
    xml.Say "Welcome #{@agent.name}."
    xml.Say "#{@message}"
    xml.Say "Please press 1 to read the task. Press 2 to check out. Press 3 to hear about Yang. Or Press 4 to hang up."
  end
end

View in app/views/twilios/no_agent.xml.builder

xml.instruct!
xml.Response do
  xml.Gather(:action => @post_to, :numDigits => 6) do  
    xml.Say "Agent could not be found. Please enter your 6 digit code."
  end
end

3. Read tasks, check out or hear about Yang

In addition to incoming and verify actions.

This is where main messages are configured and played. Depending on the option user chooses, it will either 1) say the tasks again, 2) check the user out, 3) say a few things about Yang, or 4) just hang up.

class TwiliosController < ApplicationController   

  def direction         
    @client = Client.find(params[:client_id])     
    @agent = Agent.find(params[:agent_id])     
    @message = @client.task_list     
    @post_to = BASE_URL + "/direction?agent_id=#{@agent.id}&client_id=#{@client.id}"          

    # 1 to hear the tasks again, 2 to check out, 3 to hang up.     
    if params['Digits'] == '1'       
      render :action => "direction.xml.builder", :layout => false
    elsif params['Digits'] == '2'
      @agent.check_out(@client.id)
      @goodbye_message = "Thank you for your service today."
      render :action => 'goodbye.xml.builder', :layout => false
    elsif params['Digits'] == '3'
      @message = "Yang is the most awesome guy ever - both personally and professionally. He is pretty sexy, too."
      render :action => 'direction.xml.builder', :layout => false
    elsif params['Digits'] == '4'
      @goodbye_message = "Have a great day."
      render :action => 'goodbye.xml.builder', :layout => false
    end
  end
end

View in app/views/twilios/goodbye.xml.builder

xml.instruct!
xml.Response do
  xml.Say "#{@goodbye_message}"
  xml.Say "Good-bye."
  xml.Hangup
end

Have fun!

That’s it! My codes are so not refactored and optimized. I did it to see what I can do with Twilio app. I hope this gives you a tip of what you can do with Twilio API. Go bananas!

Post to Twitter