Merge branch 'develop'
diff --git a/README.md b/README.md
index 0114abf..5c4526a 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,8 @@
 Documentation and Usage
 =======================
 
-The latest [documentation](http://rubydoc.info/github/PredictionIO/PredictionIO-Ruby-SDK/frames)
-is generously hosted by RubyDoc.info.
+[Ruby quickstart guide](http://docs.prediction.io/current/tutorials/quickstart-ruby.html)
+
+An online version of the documentation is [viewable here](http://docs.prediction.io/ruby/api/PredictionIO.html).
 
 A local copy of the documentation will also be installed by the gem.
diff --git a/lib/predictionio/client.rb b/lib/predictionio/client.rb
index 13b8664..4ef2992 100644
--- a/lib/predictionio/client.rb
+++ b/lib/predictionio/client.rb
@@ -151,6 +151,9 @@
     # Raised when ItemRec results cannot be found for a user after a synchronous API call.
     class ItemRecNotFoundError < StandardError; end
 
+    # Raised when ItemRank results cannot be found for a user after a synchronous API call.
+    class ItemRankNotFoundError < StandardError; end
+
     # Raised when ItemSim results cannot be found for an item after a synchronous API call.
     class ItemSimNotFoundError < StandardError; end
 
@@ -158,9 +161,9 @@
     class U2IActionNotCreatedError < StandardError; end
 
     # Create a new PredictionIO client with default:
-    # - API entry point at http://localhost:8000
-    # - API return data format of json
-    # - 10 concurrent HTTP(S) connections
+    # - 10 concurrent HTTP(S) connections (threads)
+    # - API entry point at http://localhost:8000 (apiurl)
+    # - a 60-second timeout for each HTTP(S) connection (thread_timeout)
     def initialize(appkey, threads = 10, apiurl = "http://localhost:8000", thread_timeout = 60)
       @appkey = appkey
       @apiformat = "json"
@@ -532,6 +535,66 @@
     end
 
     # :category: Asynchronous Methods
+    # Asynchronously request to get the ranking for a user from an ItemRank engine and return a PredictionIO::AsyncResponse object immediately.
+    #
+    # Corresponding REST API method: GET /engines/itemrank/:engine/ranked
+    #
+    # See also #get_itemrank_ranked.
+    def aget_itemrank_ranked(engine, iids, params = {})
+      rparams = Hash.new
+      rparams["pio_appkey"] = @appkey
+      rparams["pio_uid"] = @apiuid
+      if iids.kind_of?(Array) && iids.any?
+        rparams["pio_iids"] = iids.join(",")
+      else
+        rparams["pio_iids"] = iids
+      end
+      if params["pio_attributes"]
+        if params["pio_attributes"].kind_of?(Array) && params["pio_attributes"].any?
+          rparams["pio_attributes"] = params["pio_attributes"].join(",")
+        else
+          rparams["pio_attributes"] = params["pio_attributes"]
+        end
+      end
+      @http.aget(PredictionIO::AsyncRequest.new("/engines/itemrank/#{engine}/ranked.#{@apiformat}", rparams))
+    end
+
+    # :category: Synchronous Methods
+    # Synchronously request to get the ranking for a user from an ItemRank engine and block until a response is received.
+    #
+    # See #aget_itemrank_ranked for a description of special argument handling.
+    #
+    # call-seq:
+    # aget_itemrank_ranked(engine, n, params = {})
+    # aget_itemrank_ranked(async_response)
+    def get_itemrank_ranked(*args)
+      uid_or_res = args[0]
+      if uid_or_res.is_a?(PredictionIO::AsyncResponse)
+        response = uid_or_res
+      else
+        response = aget_itemrank_ranked(*args)
+      end
+      http_response = response.get
+      if http_response.is_a?(Net::HTTPOK)
+        res = JSON.parse(http_response.body)
+        if response.request.params.has_key?('pio_attributes')
+          attributes = response.request.params['pio_attributes'].split(',')
+          list_of_attribute_values = attributes.map { |attrib| res[attrib] }
+          res["pio_iids"].zip(*list_of_attribute_values).map { |v| Hash[(['pio_iid'] + attributes).zip(v)] }
+        else
+          res["pio_iids"]
+        end
+      else
+        begin
+          msg = response.body
+        rescue Exception
+          raise ItemRankNotFoundError, response
+        end
+        raise ItemRankNotFoundError, msg
+      end
+    end
+
+    # :category: Asynchronous Methods
     # Asynchronously request to get the top n similar items for an item from an ItemSim engine and return a PredictionIO::AsyncResponse object immediately.
     #
     # Corresponding REST API method: GET /engines/itemsim/:engine/topn
diff --git a/lib/predictionio/connection.rb b/lib/predictionio/connection.rb
index 9823aff..d9bdc28 100644
--- a/lib/predictionio/connection.rb
+++ b/lib/predictionio/connection.rb
@@ -13,7 +13,7 @@
     attr_reader :timeout
 
     # Spawns a number of threads with persistent HTTP connection to the specified URI.
-    # Sets a default timeout of 5 seconds.
+    # Sets a default timeout of 60 seconds.
     def initialize(uri, threads = 1, timeout = 60)
       @packages = Queue.new
       @counter_lock = Mutex.new
diff --git a/predictionio.gemspec b/predictionio.gemspec
index 96f67f6..5c612e4 100644
--- a/predictionio.gemspec
+++ b/predictionio.gemspec
@@ -6,7 +6,7 @@
 provides convenient access of the PredictionIO API to Ruby programmers so that
 they can focus on their application logic.
   EOF
-  s.version = "0.7.0"
+  s.version = "0.7.1"
   s.author = "The PredictionIO Team"
   s.email = "support@prediction.io"
   s.homepage = "http://prediction.io"
diff --git a/spec/predictionio_spec.rb b/spec/predictionio_spec.rb
index a58cc92..0e75ad6 100644
--- a/spec/predictionio_spec.rb
+++ b/spec/predictionio_spec.rb
@@ -58,6 +58,22 @@
     end
   end
 
+  describe 'Item Rank API' do
+    it 'provides ranking to a user without attributes' do
+      client.identify("foo")
+      response = client.get_itemrank_ranked("itemrank-engine", ["y", "z", "x"])
+      expect(response).to eq(["x", "y", "z"])
+    end
+    it 'provides ranking to a user with attributes' do
+      client.identify("foo")
+      response = client.get_itemrank_ranked("itemrank-engine", ["y", "x", "z"], 'pio_attributes' => 'name')
+      expect(response).to eq([
+        {"pio_iid" => "x", "name" => "a"},
+        {"pio_iid" => "y", "name" => "b"},
+        {"pio_iid" => "z", "name" => "c"}])
+    end
+  end
+
   describe 'Item Similarity API' do
     it 'provides similarities to an item without attributes' do
       response = client.get_itemsim_top_n("itemsim-engine", "bar", 10)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 15f35e8..2cc038a 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -43,6 +43,15 @@
       with(:query => hash_including("pio_appkey" => "foobar", "pio_n" => "10", "pio_uid" => "foo", 'pio_attributes' => 'name')).
       to_return(:status => 200, :body => JSON.generate({"pio_iids" => ["x", "y", "z"], "name" => ["a", "b", "c"]}), :headers => {})
 
+    # Item Recommendation API
+    stub_request(:get, "http://fakeapi.com:8000/engines/itemrank/itemrank-engine/ranked.json").
+      with(:query => hash_including("pio_appkey" => "foobar", "pio_iids" => "y,z,x", "pio_uid" => "foo")).
+      to_return(:status => 200, :body => JSON.generate({"pio_iids" => ["x", "y", "z"]}), :headers => {})
+
+    stub_request(:get, "http://fakeapi.com:8000/engines/itemrank/itemrank-engine/ranked.json").
+      with(:query => hash_including("pio_appkey" => "foobar", "pio_iids" => "y,x,z", "pio_uid" => "foo", 'pio_attributes' => 'name')).
+      to_return(:status => 200, :body => JSON.generate({"pio_iids" => ["x", "y", "z"], "name" => ["a", "b", "c"]}), :headers => {})
+
     # Item Similarity API
     stub_request(:get, "http://fakeapi.com:8000/engines/itemsim/itemsim-engine/topn.json").
       with(:query => hash_including("pio_appkey" => "foobar", "pio_n" => "10", "pio_iid" => "bar")).