Merge branch 'develop'
diff --git a/Gemfile b/Gemfile
index 32c3ae0..783e457 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,4 @@
-source "https://rubygems.org"
+source 'https://rubygems.org'
group :test do
gem 'coveralls', require: false
gem 'rspec', '~> 2.14.1'
diff --git a/Rakefile b/Rakefile
index 70a846d..f34a48c 100644
--- a/Rakefile
+++ b/Rakefile
@@ -2,4 +2,4 @@
RSpec::Core::RakeTask.new(:spec)
-task :default => :spec
+task default: :spec
diff --git a/lib/predictionio.rb b/lib/predictionio.rb
index 235c10c..73250bd 100644
--- a/lib/predictionio.rb
+++ b/lib/predictionio.rb
@@ -1 +1,38 @@
-require "predictionio/client"
+require 'predictionio/client'
+require 'predictionio/event_client'
+require 'predictionio/engine_client'
+
+# The PredictionIO module contains classes that provide convenient access of
+# PredictionIO Event API and Engine Instance over HTTP/HTTPS.
+#
+# To create an app and perform predictions, please download the PredictionIO
+# suite from http://prediction.io.
+#
+# Most functionality is provided by PredictionIO::EventClient and
+# PredictionIO::EngineClient classes.
+#
+# == Deprecation Notice
+#
+# Pre-0.7.x series support is now deprecated. All existing users are strongly
+# encouraged to migrate to 0.8.x.
+#
+# The old Client interface is retained in case of any accidental Gem upgrade.
+# It will be removed in the next minor version.
+#
+# == High-performance Asynchronous Backend
+#
+# All REST request methods come in both synchronous and asynchronous flavors.
+# Both flavors accept the same set of arguments. In addition, all synchronous
+# request methods can instead accept a PredictionIO::AsyncResponse object
+# generated from asynchronous request methods as its first argument. In this
+# case, the method will block until a response is received from it.
+#
+# Any network reconnection and request retry is automatically handled in the
+# background. Exceptions will be thrown after a request times out to avoid
+# infinite blocking.
+#
+# == Installation
+# The easiest way is to use RubyGems:
+# gem install predictionio
+module PredictionIO
+end
diff --git a/lib/predictionio/async_request.rb b/lib/predictionio/async_request.rb
index ea89416..a2c1b5e 100644
--- a/lib/predictionio/async_request.rb
+++ b/lib/predictionio/async_request.rb
@@ -1,5 +1,6 @@
module PredictionIO
- # This class contains the URI path and query parameters that is consumed by PredictionIO::Connection for asynchronous HTTP requests.
+ # This class contains the URI path and query parameters that is consumed by
+ # PredictionIO::Connection for asynchronous HTTP requests.
class AsyncRequest
# The path portion of the request URI.
@@ -8,7 +9,8 @@
# Query parameters, or form data.
attr_reader :params
- # Populates the package with request URI path, and optionally query parameters or form data.
+ # Populates the package with request URI path, and optionally query
+ # parameters or form data.
def initialize(path, params = {})
@params = params
@path = path
diff --git a/lib/predictionio/client.rb b/lib/predictionio/client.rb
index 4ef2992..e2b405b 100644
--- a/lib/predictionio/client.rb
+++ b/lib/predictionio/client.rb
@@ -11,13 +11,7 @@
require 'predictionio/async_response'
require 'predictionio/connection'
-# The PredictionIO module contains classes that provide convenient access of the PredictionIO output API over HTTP/HTTPS.
-#
-# To create an app and perform predictions, please download the PredictionIO suite from http://prediction.io.
-#
-# Most functionality is provided by the PredictionIO::Client class.
module PredictionIO
-
# This class contains methods that access PredictionIO via REST requests.
#
# Many REST request methods support optional arguments.
@@ -505,8 +499,8 @@
# See #aget_itemrec_top_n for a description of special argument handling.
#
# call-seq:
- # aget_itemrec_top_n(engine, n, params = {})
- # aget_itemrec_top_n(async_response)
+ # get_itemrec_top_n(engine, n, params = {})
+ # get_itemrec_top_n(async_response)
def get_itemrec_top_n(*args)
uid_or_res = args[0]
if uid_or_res.is_a?(PredictionIO::AsyncResponse)
@@ -565,8 +559,8 @@
# See #aget_itemrank_ranked for a description of special argument handling.
#
# call-seq:
- # aget_itemrank_ranked(engine, n, params = {})
- # aget_itemrank_ranked(async_response)
+ # get_itemrank_ranked(engine, n, params = {})
+ # get_itemrank_ranked(async_response)
def get_itemrank_ranked(*args)
uid_or_res = args[0]
if uid_or_res.is_a?(PredictionIO::AsyncResponse)
@@ -633,8 +627,8 @@
# See #aget_itemsim_top_n for a description of special argument handling.
#
# call-seq:
- # aget_itemsim_top_n(engine, iid, n, params = {})
- # aget_itemsim_top_n(async_response)
+ # get_itemsim_top_n(engine, iid, n, params = {})
+ # get_itemsim_top_n(async_response)
def get_itemsim_top_n(*args)
uid_or_res = args[0]
if uid_or_res.is_a?(PredictionIO::AsyncResponse)
diff --git a/lib/predictionio/connection.rb b/lib/predictionio/connection.rb
index d9bdc28..2644bb9 100644
--- a/lib/predictionio/connection.rb
+++ b/lib/predictionio/connection.rb
@@ -34,29 +34,34 @@
request = package[:request]
response = package[:response]
case package[:method]
- when "get"
+ when 'get'
http_req = Net::HTTP::Get.new("#{uri.path}#{request.qpath}")
begin
response.set(http.request(http_req))
rescue Exception => details
response.set(details)
end
- when "post"
- http_req = Net::HTTP::Post.new("#{uri.path}#{request.path}")
- http_req.set_form_data(request.params)
+ when 'post'
+ if request.params.is_a?(Hash)
+ http_req = Net::HTTP::Post.new("#{uri.path}#{request.path}")
+ http_req.set_form_data(request.params)
+ else
+ http_req = Net::HTTP::Post.new("#{uri.path}#{request.path}", initheader = {'Content-Type' => 'application/json'})
+ http_req.body = request.params
+ end
begin
response.set(http.request(http_req))
rescue Exception => details
response.set(details)
end
- when "delete"
+ when 'delete'
http_req = Net::HTTP::Delete.new("#{uri.path}#{request.qpath}")
begin
response.set(http.request(http_req))
rescue Exception => details
response.set(details)
end
- when "exit"
+ when 'exit'
@counter_lock.synchronize do
@connections -= 1
end
@@ -96,17 +101,17 @@
# Shortcut to create an asynchronous GET request with the response object returned.
def aget(areq)
- request("get", areq)
+ request('get', areq)
end
# Shortcut to create an asynchronous POST request with the response object returned.
def apost(areq)
- request("post", areq)
+ request('post', areq)
end
# Shortcut to create an asynchronous DELETE request with the response object returned.
def adelete(areq)
- request("delete", areq)
+ request('delete', areq)
end
end
end
diff --git a/lib/predictionio/engine_client.rb b/lib/predictionio/engine_client.rb
new file mode 100644
index 0000000..021db1d
--- /dev/null
+++ b/lib/predictionio/engine_client.rb
@@ -0,0 +1,127 @@
+# Ruby SDK for convenient access of PredictionIO Output API.
+#
+# Author:: PredictionIO Team (support@prediction.io)
+# Copyright:: Copyright (c) 2014 TappingStone, Inc.
+# License:: Apache License, Version 2.0
+
+require 'predictionio/async_request'
+require 'predictionio/async_response'
+require 'predictionio/connection'
+
+module PredictionIO
+ # This class contains methods that interface with PredictionIO Engine
+ # Instances that are trained from PredictionIO built-in Engines.
+ #
+ # Many REST request methods support optional arguments. They can be supplied
+ # to these methods as Hash'es. For a complete reference, please visit
+ # http://prediction.io.
+ #
+ # == Synopsis
+ # In most cases, using synchronous methods. If you have a special performance
+ # requirement, you may want to take a look at asynchronous methods.
+ #
+ # === Instantiate an EngineClient
+ # # Include the PredictionIO SDK
+ # require 'predictionio'
+ #
+ # client = PredictionIO::EngineClient.new
+ #
+ # === Send a Query to Retrieve Predictions
+ # # PredictionIO call to record the view action
+ # begin
+ # result = client.query('uid' => 'foobar')
+ # rescue NotFoundError => e
+ # ...
+ # rescue BadRequestError => e
+ # ...
+ # rescue ServerError => e
+ # ...
+ # end
+ class EngineClient
+ # Raised when an event is not created after a synchronous API call.
+ class NotFoundError < StandardError; end
+
+ # Raised when the query is malformed.
+ class BadRequestError < StandardError; end
+
+ # Raised when the Engine Instance returns a server error.
+ class ServerError < StandardError; end
+
+ # Create a new PredictionIO Event Client with defaults:
+ # - 1 concurrent HTTP(S) connections (threads)
+ # - API entry point at http://localhost:7070 (apiurl)
+ # - a 60-second timeout for each HTTP(S) connection (thread_timeout)
+ def initialize(apiurl = 'http://localhost:8000', threads = 1,
+ thread_timeout = 60)
+ @http = PredictionIO::Connection.new(URI(apiurl), threads, thread_timeout)
+ end
+
+ # Returns the number of pending requests within the current client.
+ def pending_requests
+ @http.packages.size
+ end
+
+ # Returns PredictionIO's status in string.
+ def get_status
+ status = @http.aget(PredictionIO::AsyncRequest.new('/')).get
+ begin
+ status.body
+ rescue
+ status
+ end
+ end
+
+ protected
+
+ # Internal helper method. Do not call directly.
+ def sync_events(sync_m, *args)
+ if args[0].is_a?(PredictionIO::AsyncResponse)
+ response = args[0].get
+ else
+ response = send(sync_m, *args).get
+ end
+ return JSON.parse(response.body) if response.is_a?(Net::HTTPOK)
+ begin
+ msg = response.body
+ rescue
+ raise response
+ end
+ if response.is_a?(Net::HTTPBadRequest)
+ fail BadRequestError, msg
+ elsif response.is_a?(Net::HTTPNotFound)
+ fail NotFoundError, msg
+ elsif response.is_a?(Net::HTTPServerError)
+ fail ServerError, msg
+ else
+ fail msg
+ end
+ end
+
+ public
+
+ # :category: Asynchronous Methods
+ # Asynchronously sends a query and returns PredictionIO::AsyncResponse
+ # object immediately. The query should be a Ruby data structure that can be
+ # converted to a JSON object.
+ #
+ # Corresponding REST API method: POST /
+ #
+ # See also #send_query.
+ def asend_query(query)
+ @http.apost(PredictionIO::AsyncRequest.new('/queries.json',
+ query.to_json))
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously sends a query and block until predictions are received.
+ #
+ # See also #asend_query.
+ #
+ # call-seq:
+ # send_query(data)
+ # send_query(async_response)
+ def send_query(*args)
+ sync_events(:asend_query, *args)
+ end
+ end
+end
diff --git a/lib/predictionio/event_client.rb b/lib/predictionio/event_client.rb
new file mode 100644
index 0000000..7deaa53
--- /dev/null
+++ b/lib/predictionio/event_client.rb
@@ -0,0 +1,340 @@
+# Ruby SDK for convenient access of PredictionIO Output API.
+#
+# Author:: PredictionIO Team (support@prediction.io)
+# Copyright:: Copyright (c) 2014 TappingStone, Inc.
+# License:: Apache License, Version 2.0
+
+require 'predictionio/async_request'
+require 'predictionio/async_response'
+require 'predictionio/connection'
+require 'date'
+
+module PredictionIO
+ # This class contains methods that interface with the PredictionIO Event
+ # Server via the PredictionIO Event API using REST requests.
+ #
+ # Many REST request methods support optional arguments. They can be supplied
+ # to these methods as Hash'es. For a complete reference, please visit
+ # http://prediction.io.
+ #
+ # == High-performance Asynchronous Backend
+ #
+ # All REST request methods come in both synchronous and asynchronous flavors.
+ # Both flavors accept the same set of arguments. In addition, all synchronous
+ # request methods can instead accept a PredictionIO::AsyncResponse object
+ # generated from asynchronous request methods as its first argument. In this
+ # case, the method will block until a response is received from it.
+ #
+ # Any network reconnection and request retry is automatically handled in the
+ # background. Exceptions will be thrown after a request times out to avoid
+ # infinite blocking.
+ #
+ # == Installation
+ # The easiest way is to use RubyGems:
+ # gem install predictionio
+ #
+ # == Synopsis
+ # In most cases, using synchronous methods. If you have a special performance
+ # requirement, you may want to take a look at asynchronous methods.
+ #
+ # === Instantiate an EventClient
+ # # Include the PredictionIO SDK
+ # require 'predictionio'
+ #
+ # client = PredictionIO::EventClient.new(<app_id>)
+ #
+ # === Import a User Record from Your App (with asynchronous/non-blocking
+ # requests)
+ #
+ # #
+ # # (your user registration logic)
+ # #
+ #
+ # uid = get_user_from_your_db()
+ #
+ # # PredictionIO call to create user
+ # response = client.aset_user(uid)
+ #
+ # #
+ # # (other work to do for the rest of the page)
+ # #
+ #
+ # begin
+ # # PredictionIO call to retrieve results from an asynchronous response
+ # result = client.set_user(response)
+ # rescue PredictionIO::EventClient::NotCreatedError => e
+ # log_and_email_error(...)
+ # end
+ #
+ # === Import a User Action (Rate) from Your App (with synchronous/blocking
+ # requests)
+ # # PredictionIO call to record the view action
+ # begin
+ # result = client.record_user_action_on_item('rate', 'foouser',
+ # 'baritem',
+ # 'pio_rating' => 4)
+ # rescue PredictionIO::EventClient::NotCreatedError => e
+ # ...
+ # end
+ class EventClient
+ # Raised when an event is not created after a synchronous API call.
+ class NotCreatedError < StandardError; end
+
+ # Create a new PredictionIO Event Client with defaults:
+ # - 1 concurrent HTTP(S) connections (threads)
+ # - API entry point at http://localhost:7070 (apiurl)
+ # - a 60-second timeout for each HTTP(S) connection (thread_timeout)
+ def initialize(app_id, apiurl = 'http://localhost:7070', threads = 1,
+ thread_timeout = 60)
+ @app_id = app_id
+ @http = PredictionIO::Connection.new(URI(apiurl), threads, thread_timeout)
+ end
+
+ # Returns the number of pending requests within the current client.
+ def pending_requests
+ @http.packages.size
+ end
+
+ # Returns PredictionIO's status in string.
+ def get_status
+ status = @http.aget(PredictionIO::AsyncRequest.new('/')).get
+ begin
+ status.body
+ rescue
+ status
+ end
+ end
+
+ protected
+
+ # Internal helper method. Do not call directly.
+ def sync_events(sync_m, *args)
+ if args[0].is_a?(PredictionIO::AsyncResponse)
+ response = args[0].get
+ else
+ response = send(sync_m, *args).get
+ end
+ return response if response.is_a?(Net::HTTPCreated)
+ begin
+ msg = response.body
+ rescue
+ raise NotCreatedError, response
+ end
+ fail NotCreatedError, msg
+ end
+
+ public
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to create an event and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #create_event.
+ def acreate_event(event, entity_type, entity_id, optional = {})
+ h = optional
+ h.key?('eventTime') || h['eventTime'] = DateTime.now.to_s
+ h['appId'] = @app_id
+ h['event'] = event
+ h['entityType'] = entity_type
+ h['entityId'] = entity_id
+ @http.apost(PredictionIO::AsyncRequest.new('/events.json', h.to_json))
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to create an event and block until a response is
+ # received.
+ #
+ # See also #acreate_event.
+ #
+ # call-seq:
+ # create_event(event, entity_type, entity_id, optional = {})
+ # create_event(async_response)
+ def create_event(*args)
+ sync_events(:acreate_event, *args)
+ end
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to set properties of a user and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #set_user.
+ def aset_user(uid, optional = {})
+ acreate_event('$set', 'pio_user', uid, optional)
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to set properties of a user and block until a
+ # response is received.
+ #
+ # See also #aset_user.
+ #
+ # call-seq:
+ # set_user(uid, optional = {})
+ # set_user(async_response)
+ def set_user(*args)
+ sync_events(:aset_user, *args)
+ end
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to unset properties of a user and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # properties must be a non-empty Hash.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #unset_user.
+ def aunset_user(uid, optional)
+ optional.key?('properties') ||
+ fail(ArgumentError, 'properties must be present when event is $unset')
+ optional['properties'].empty? &&
+ fail(ArgumentError, 'properties cannot be empty when event is $unset')
+ acreate_event('$unset', 'pio_user', uid, optional)
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to unset properties of a user and block until a
+ # response is received.
+ #
+ # See also #aunset_user.
+ #
+ # call-seq:
+ # unset_user(uid, optional)
+ # unset_user(async_response)
+ def unset_user(*args)
+ sync_events(:aunset_user, *args)
+ end
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to delete a user and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #delete_user.
+ def adelete_user(uid)
+ acreate_event('$delete', 'pio_user', uid)
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to delete a user and block until a response is
+ # received.
+ #
+ # See also #adelete_user.
+ #
+ # call-seq:
+ # delete_user(uid)
+ # delete_user(async_response)
+ def delete_user(*args)
+ sync_events(:adelete_user, *args)
+ end
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to set properties of an item and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #set_item.
+ def aset_item(iid, optional = {})
+ acreate_event('$set', 'pio_item', iid, optional)
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to set properties of an item and block until a
+ # response is received.
+ #
+ # See also #aset_item.
+ #
+ # call-seq:
+ # set_item(iid, properties = {}, optional = {})
+ # set_item(async_response)
+ def set_item(*args)
+ sync_events(:aset_item, *args)
+ end
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to unset properties of an item and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # properties must be a non-empty Hash.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #unset_item.
+ def aunset_item(iid, optional)
+ optional.key?('properties') ||
+ fail(ArgumentError, 'properties must be present when event is $unset')
+ optional['properties'].empty? &&
+ fail(ArgumentError, 'properties cannot be empty when event is $unset')
+ acreate_event('$unset', 'pio_item', iid, optional)
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to unset properties of an item and block until a
+ # response is received.
+ #
+ # See also #aunset_item.
+ #
+ # call-seq:
+ # unset_item(iid, properties, optional = {})
+ # unset_item(async_response)
+ def unset_item(*args)
+ sync_events(:aunset_item, *args)
+ end
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to delete an item and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #delete_item.
+ def adelete_item(uid)
+ acreate_event('$delete', 'pio_item', uid)
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to delete an item and block until a response is
+ # received.
+ #
+ # See also #adelete_item.
+ #
+ # call-seq:
+ # delete_item(uid)
+ # delete_item(async_response)
+ def delete_item(*args)
+ sync_events(:adelete_item, *args)
+ end
+
+ # :category: Asynchronous Methods
+ # Asynchronously request to record an action on an item and return a
+ # PredictionIO::AsyncResponse object immediately.
+ #
+ # Corresponding REST API method: POST /events.json
+ #
+ # See also #record_user_action_on_item.
+ def arecord_user_action_on_item(action, uid, iid, optional = {})
+ optional['targetEntityType'] = 'pio_item'
+ optional['targetEntityId'] = iid
+ acreate_event(action, 'pio_user', uid, optional)
+ end
+
+ # :category: Synchronous Methods
+ # Synchronously request to record an action on an item and block until a
+ # response is received.
+ #
+ # See also #arecord_user_action_on_item.
+ #
+ # call-seq:
+ # record_user_action_on_item(action, uid, iid, optional = {})
+ # record_user_action_on_item(async_response)
+ def record_user_action_on_item(*args)
+ sync_events(:arecord_user_action_on_item, *args)
+ end
+ end
+end
diff --git a/predictionio.gemspec b/predictionio.gemspec
index 5c612e4..b9a2596 100644
--- a/predictionio.gemspec
+++ b/predictionio.gemspec
@@ -1,15 +1,16 @@
Gem::Specification.new do |s|
- s.name = "predictionio"
- s.summary = "PredictionIO Ruby SDK"
+ s.name = 'predictionio'
+ s.summary = 'PredictionIO Ruby SDK'
s.description = <<-EOF
PredictionIO is a prediction server for building smart applications. This gem
provides convenient access of the PredictionIO API to Ruby programmers so that
they can focus on their application logic.
EOF
- s.version = "0.7.1"
- s.author = "The PredictionIO Team"
- s.email = "support@prediction.io"
- s.homepage = "http://prediction.io"
+ s.version = '0.8.0'
+ s.licenses = ['Apache-2.0']
+ s.author = 'The PredictionIO Team'
+ s.email = 'support@prediction.io'
+ s.homepage = 'http://prediction.io'
s.platform = Gem::Platform::RUBY
s.required_ruby_version = '>=1.9'
s.files = Dir[File.join('lib', '**', '**')]
diff --git a/spec/predictionio_spec.rb b/spec/predictionio_spec.rb
index 0e75ad6..911dc93 100644
--- a/spec/predictionio_spec.rb
+++ b/spec/predictionio_spec.rb
@@ -1,90 +1,141 @@
require 'predictionio'
require 'spec_helper'
-client = PredictionIO::Client.new("foobar", 10, "http://fakeapi.com:8000")
+client = PredictionIO::Client.new('foobar', 10, 'http://fakeapi.com:8000')
+event_client = PredictionIO::EventClient.new(1, 'http://fakeapi.com:8000', 10)
+engine_client = PredictionIO::EngineClient.new('http://fakeapi.com:8000', 10)
describe PredictionIO do
+ describe 'Events API' do
+ it 'create_event should create an event' do
+ response = event_client.create_event('register', 'user', 'foobar')
+ expect(response).to_not raise_error
+ end
+ it 'set_user should set user properties' do
+ response = event_client.set_user('foobar')
+ expect(response).to_not raise_error
+ end
+ it 'unset_user should unset user properties' do
+ response = event_client.unset_user('foobar',
+ 'properties' => { 'bar' => 'baz' })
+ expect(response).to_not raise_error
+ end
+ it 'set_item should set item properties' do
+ response = event_client.set_item('foobar')
+ expect(response).to_not raise_error
+ end
+ it 'unset_item should unset item properties' do
+ response = event_client.unset_item('foobar',
+ 'properties' => { 'bar' => 'baz' })
+ expect(response).to_not raise_error
+ end
+ it 'record_user_action_on_item should record a U2I action' do
+ response = event_client.record_user_action_on_item(
+ 'greet', 'foobar', 'barbaz', 'properties' => { 'dead' => 'beef' })
+ expect(response).to_not raise_error
+ end
+ it 'delete_user should delete a user' do
+ response = event_client.delete_user('foobar')
+ expect(response).to_not raise_error
+ end
+ it 'delete_item should delete an item' do
+ response = event_client.delete_item('foobar')
+ expect(response).to_not raise_error
+ end
+ end
+
+ describe 'Engine Client' do
+ it 'send_query should get predictions' do
+ predictions = engine_client.send_query('uid' => 'foobar')
+ expect(predictions).to eq('iids' => %w(dead beef))
+ end
+ end
+
describe 'Users API' do
it 'create_user should create a user' do
- response = client.create_user("foo")
+ response = client.create_user('foo')
expect(response).to_not raise_error
end
it 'get_user should get a user' do
- response = client.get_user("foo")
- expect(response).to eq({"pio_uid" => "foo"})
+ response = client.get_user('foo')
+ expect(response).to eq('pio_uid' => 'foo')
end
it 'delete_user should delete a user' do
- response = client.delete_user("foo")
+ response = client.delete_user('foo')
expect(response).to_not raise_error
end
end
describe 'Items API' do
it 'create_item should create a item' do
- response = client.create_item("bar", ["dead", "beef"])
+ response = client.create_item('bar', %w(dead beef))
expect(response).to_not raise_error
end
it 'get_item should get a item' do
- response = client.get_item("bar")
- expect(response).to eq({"pio_iid" => "bar", "pio_itypes" => ["dead", "beef"]})
+ response = client.get_item('bar')
+ expect(response).to eq('pio_iid' => 'bar',
+ 'pio_itypes' => %w(dead beef))
end
it 'delete_item should delete a item' do
- response = client.delete_item("bar")
+ response = client.delete_item('bar')
expect(response).to_not raise_error
end
end
describe 'U2I API' do
it 'record_action_on_item should record an action' do
- client.identify("foo")
- response = client.record_action_on_item("view", "bar")
+ client.identify('foo')
+ response = client.record_action_on_item('view', 'bar')
expect(response).to_not raise_error
end
end
describe 'Item Recommendation API' do
it 'provides recommendations to a user without attributes' do
- client.identify("foo")
- response = client.get_itemrec_top_n("itemrec-engine", 10)
- expect(response).to eq(["x", "y", "z"])
+ client.identify('foo')
+ response = client.get_itemrec_top_n('itemrec-engine', 10)
+ expect(response).to eq(%w(x y z))
end
it 'provides recommendations to a user with attributes' do
- client.identify("foo")
- response = client.get_itemrec_top_n("itemrec-engine", 10, 'pio_attributes' => 'name')
+ client.identify('foo')
+ response = client.get_itemrec_top_n('itemrec-engine', 10,
+ 'pio_attributes' => 'name')
expect(response).to eq([
- {"pio_iid" => "x", "name" => "a"},
- {"pio_iid" => "y", "name" => "b"},
- {"pio_iid" => "z", "name" => "c"}])
+ { 'pio_iid' => 'x', 'name' => 'a' },
+ { 'pio_iid' => 'y', 'name' => 'b' },
+ { 'pio_iid' => 'z', 'name' => 'c' }])
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"])
+ client.identify('foo')
+ response = client.get_itemrank_ranked('itemrank-engine', %w(y z x))
+ expect(response).to eq(%w(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')
+ client.identify('foo')
+ response = client.get_itemrank_ranked('itemrank-engine', %w(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"}])
+ { '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)
- expect(response).to eq(["x", "y", "z"])
+ response = client.get_itemsim_top_n('itemsim-engine', 'bar', 10)
+ expect(response).to eq(%w(x y z))
end
it 'provides similarities to an item with attributes' do
- response = client.get_itemsim_top_n("itemsim-engine", "bar", 10, 'pio_attributes' => 'name')
+ response = client.get_itemsim_top_n('itemsim-engine', 'bar', 10,
+ 'pio_attributes' => 'name')
expect(response).to eq([
- {"pio_iid" => "x", "name" => "a"},
- {"pio_iid" => "y", "name" => "b"},
- {"pio_iid" => "z", "name" => "c"}])
+ { 'pio_iid' => 'x', 'name' => 'a' },
+ { 'pio_iid' => 'y', 'name' => 'b' },
+ { 'pio_iid' => 'z', 'name' => 'c' }])
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 2cc038a..19d9d32 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -7,58 +7,136 @@
RSpec.configure do |config|
config.before(:each) do
+ # Events API
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: 'register',
+ entityType: 'user', entityId: 'foobar'))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef00'))
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: '$set',
+ entityType: 'pio_user', entityId: 'foobar'))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef01'))
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: '$unset',
+ entityType: 'pio_user', entityId: 'foobar',
+ properties: { bar: 'baz' }))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef02'))
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: '$set',
+ entityType: 'pio_item', entityId: 'foobar'))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef03'))
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: '$unset',
+ entityType: 'pio_item', entityId: 'foobar',
+ properties: { bar: 'baz' }))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef04'))
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: 'greet',
+ entityType: 'pio_user', entityId: 'foobar',
+ targetEntityType: 'pio_item',
+ targetEntityId: 'barbaz',
+ properties: { dead: 'beef' }))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef05'))
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: '$delete',
+ entityType: 'pio_user', entityId: 'foobar'))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef06'))
+ stub_request(:post, 'http://fakeapi.com:8000/events.json')
+ .with(body: hash_including(appId: 1, event: '$delete',
+ entityType: 'pio_item', entityId: 'foobar'))
+ .to_return(status: 201, body: JSON.generate(eventId: 'deadbeef07'))
+
+ # Engine Instance
+ stub_request(:post, 'http://fakeapi.com:8000/queries.json')
+ .with(body: { uid: 'foobar' })
+ .to_return(status: 200, body: JSON.generate(iids: %w(dead beef)),
+ headers: {})
+
# Users API
- stub_request(:post, "http://fakeapi.com:8000/users.json").
- with(:body => {"pio_appkey" => "foobar", "pio_uid" => "foo"}).
- to_return(:status => 201, :body => "", :headers => {})
- stub_request(:get, "http://fakeapi.com:8000/users/foo.json").
- with(:query => hash_including({"pio_appkey" => "foobar"})).
- to_return(:status => 200, :body => JSON.generate({"pio_uid" => "foo"}), :headers => {})
- stub_request(:delete, "http://fakeapi.com:8000/users/foo.json").
- with(:query => hash_including({"pio_appkey" => "foobar"})).
- to_return(:status => 200, :body => "", :headers => {})
+ stub_request(:post, 'http://fakeapi.com:8000/users.json')
+ .with(body: { pio_appkey: 'foobar', pio_uid: 'foo' })
+ .to_return(status: 201, body: '', headers: {})
+ stub_request(:get, 'http://fakeapi.com:8000/users/foo.json')
+ .with(query: hash_including(pio_appkey: 'foobar'))
+ .to_return(status: 200, body: JSON.generate(pio_uid: 'foo'), headers: {})
+ stub_request(:delete, 'http://fakeapi.com:8000/users/foo.json')
+ .with(query: hash_including(pio_appkey: 'foobar'))
+ .to_return(status: 200, body: '', headers: {})
# Items API
- stub_request(:post, "http://fakeapi.com:8000/items.json").
- with(:body => {"pio_appkey" => "foobar", "pio_iid" => "bar", "pio_itypes" => "dead,beef"}).
- to_return(:status => 201, :body => "", :headers => {})
- stub_request(:get, "http://fakeapi.com:8000/items/bar.json").
- with(:query => hash_including({"pio_appkey" => "foobar"})).
- to_return(:status => 200, :body => JSON.generate({"pio_iid" => "bar", "pio_itypes" => ["dead", "beef"]}), :headers => {})
- stub_request(:delete, "http://fakeapi.com:8000/items/bar.json").
- with(:query => hash_including({"pio_appkey" => "foobar"})).
- to_return(:status => 200, :body => "", :headers => {})
+ stub_request(:post, 'http://fakeapi.com:8000/items.json')
+ .with(body: { pio_appkey: 'foobar', pio_iid: 'bar',
+ pio_itypes: 'dead,beef' })
+ .to_return(status: 201, body: '', headers: {})
+ stub_request(:get, 'http://fakeapi.com:8000/items/bar.json')
+ .with(query: hash_including(pio_appkey: 'foobar'))
+ .to_return(status: 200,
+ body: JSON.generate(pio_iid: 'bar', pio_itypes: %w(dead beef)),
+ headers: {})
+ stub_request(:delete, 'http://fakeapi.com:8000/items/bar.json')
+ .with(query: hash_including(pio_appkey: 'foobar'))
+ .to_return(status: 200, body: '', headers: {})
# U2I Actions API
- stub_request(:post, "http://fakeapi.com:8000/actions/u2i.json").
- with(:body => {"pio_action" => "view", "pio_appkey" => "foobar", "pio_iid" => "bar", "pio_uid" => "foo"}).
- to_return(:status => 201, :body => "", :headers => {})
+ stub_request(:post, 'http://fakeapi.com:8000/actions/u2i.json')
+ .with(body: { pio_action: 'view', pio_appkey: 'foobar', pio_iid: 'bar',
+ pio_uid: 'foo' })
+ .to_return(status: 201, body: '', headers: {})
# Item Recommendation API
- stub_request(:get, "http://fakeapi.com:8000/engines/itemrec/itemrec-engine/topn.json").
- with(:query => hash_including("pio_appkey" => "foobar", "pio_n" => "10", "pio_uid" => "foo")).
- to_return(:status => 200, :body => JSON.generate({"pio_iids" => ["x", "y", "z"]}), :headers => {})
+ stub_request(
+ :get,
+ 'http://fakeapi.com:8000/engines/itemrec/itemrec-engine/topn.json')
+ .with(query: hash_including(pio_appkey: 'foobar', pio_n: '10',
+ pio_uid: 'foo'))
+ .to_return(status: 200, body: JSON.generate(pio_iids: %w(x y z)),
+ headers: {})
- stub_request(:get, "http://fakeapi.com:8000/engines/itemrec/itemrec-engine/topn.json").
- 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 => {})
+ stub_request(
+ :get,
+ 'http://fakeapi.com:8000/engines/itemrec/itemrec-engine/topn.json')
+ .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: %w(x y z), name: %w(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,z,x',
+ pio_uid: 'foo'))
+ .to_return(status: 200, body: JSON.generate(pio_iids: %w(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 => {})
+ 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: %w(x y z), name: %w(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")).
- to_return(:status => 200, :body => JSON.generate({"pio_iids" => ["x", "y", "z"]}), :headers => {})
+ 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'))
+ .to_return(status: 200,
+ body: JSON.generate(pio_iids: %w(x y z)),
+ headers: {})
- 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", 'pio_attributes' => 'name')).
- to_return(:status => 200, :body => JSON.generate({"pio_iids" => ["x", "y", "z"], 'name' => ['a', 'b', 'c']}), :headers => {})
+ 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', pio_attributes: 'name'))
+ .to_return(status: 200,
+ body: JSON.generate('pio_iids' => %w(x y z),
+ 'name' => %w(a b c)),
+ headers: {})
end
end