Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace HTTParty with net/http #42

Merged
merged 3 commits into from
Mar 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,3 @@ rvm:
- jruby-head
- ree
- ruby-head
- rbx-18mode
- rbx-19mode
1 change: 0 additions & 1 deletion customerio.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Gem::Specification.new do |gem|
gem.require_paths = ["lib"]
gem.version = Customerio::VERSION

gem.add_dependency('httparty', ["< 0.12", ">= 0.5"])
gem.add_dependency('multi_json', "~> 1.0")

gem.add_development_dependency('rake')
Expand Down
1 change: 1 addition & 0 deletions lib/customerio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

module Customerio
require "customerio/client"
require "customerio/param_encoder"
end
73 changes: 53 additions & 20 deletions lib/customerio/client.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
require 'httparty'
require 'net/http'
require 'multi_json'

module Customerio
class Client
include HTTParty
base_uri 'https://track.customer.io'
default_timeout 10
DEFAULT_BASE_URI = 'https://track.customer.io'
DEFAULT_TIMEOUT = 10

class Client
class MissingIdAttributeError < RuntimeError; end
class InvalidRequest < RuntimeError; end
class InvalidResponse < RuntimeError
attr_reader :response

Expand All @@ -18,16 +18,19 @@ def initialize(message, response)
end

def initialize(site_id, secret_key, options = {})
@auth = { :username => site_id, :password => secret_key }
@username = site_id
@password = secret_key
@json = options.has_key?(:json) ? options[:json] : true
@base_uri = options[:base_uri] || DEFAULT_BASE_URI
@timeout = options[:timeout] || DEFAULT_TIMEOUT
end

def identify(attributes)
create_or_update(attributes)
end

def delete(customer_id)
verify_response(self.class.delete(customer_path(customer_id), options))
verify_response(request(:delete, customer_path(customer_id)))
end

def track(*args)
Expand Down Expand Up @@ -59,11 +62,7 @@ def create_or_update(attributes = {})

url = customer_path(attributes[:id])

if @json
verify_response(self.class.put(url, options.merge(:body => MultiJson.dump(attributes), :headers => {'Content-Type' => 'application/json'})))
else
verify_response(self.class.put(url, options.merge(:body => attributes, :headers => {'Content-Type' => 'application/x-www-form-urlencoded'})))
end
verify_response(request(:put, url, attributes))
end

def create_customer_event(customer_id, event_name, attributes = {})
Expand All @@ -77,11 +76,7 @@ def create_anonymous_event(event_name, attributes = {})
def create_event(url, event_name, attributes = {})
body = { :name => event_name, :data => attributes }
body[:timestamp] = attributes[:timestamp] if valid_timestamp?(attributes[:timestamp])
if @json
verify_response(self.class.post(url, options.merge(:body => MultiJson.dump(body), :headers => {'Content-Type' => 'application/json'})))
else
verify_response(self.class.post(url, options.merge(:body => body, :headers => {'Content-Type' => 'application/x-www-form-urlencoded'})))
end
verify_response(request(:post, url, body))
end

def customer_path(id)
Expand All @@ -94,7 +89,7 @@ def valid_timestamp?(timestamp)


def verify_response(response)
if response.code >= 200 && response.code < 300
if response.code.to_i >= 200 && response.code.to_i < 300
response
else
raise InvalidResponse.new("Customer.io API returned an invalid response: #{response.code}", response)
Expand All @@ -106,8 +101,46 @@ def extract_attributes(args)
hash.inject({}){ |hash, (k,v)| hash[k.to_sym] = v; hash }
end

def options
{ :basic_auth => @auth }
def request(method, path, body = nil, headers = {})
uri = URI.join(@base_uri, path)

session = Net::HTTP.new(uri.host, uri.port)
session.use_ssl = (uri.scheme == 'https')
session.open_timeout = @timeout
session.read_timeout = @timeout

req = request_class(method).new(uri.path)
req.initialize_http_header(headers)
req.basic_auth @username, @password

add_request_body(req, body) unless body.nil?

session.start do |http|
http.request(req)
end
end

def request_class(method)
case method
when :post
Net::HTTP::Post
when :put
Net::HTTP::Put
when :delete
Net::HTTP::Delete
else
raise InvalidRequest.new("Invalid request method #{method.inspect}")
end
end

def add_request_body(req, body)
if @json
req.add_field('Content-Type', 'application/json')
req.body = MultiJson.dump(body)
else
req.add_field('Content-Type', 'application/x-www-form-urlencoded')
req.body = ParamEncoder.to_params(body)
end
end
end
end
68 changes: 68 additions & 0 deletions lib/customerio/param_encoder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Based on HTTParty's HashConversions:
#
# https://github.com/jnunemaker/httparty/blob/master/lib/httparty/hash_conversions.rb
#
# License: MIT https://github.com/jnunemaker/httparty/blob/master/MIT-LICENSE

require 'erb'
require 'uri'

module Customerio
class ParamEncoder
# @return <String> This hash as a query string
#
# @example
# { name: "Bob",
# address: {
# street: '111 Ruby Ave.',
# city: 'Ruby Central',
# phones: ['111-111-1111', '222-222-2222']
# }
# }.to_params
# #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
def self.to_params(hash)
hash.to_hash.map { |k, v| normalize_param(k, v) }.join.chop
end

# @param key<Object> The key for the param.
# @param value<Object> The value for the param.
#
# @return <String> This key value pair as a param
#
# @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
def self.normalize_param(key, value)
param = ''
stack = []

if value.respond_to?(:to_ary)
param << value.to_ary.map { |element| normalize_param("#{key}[]", element) }.join
elsif value.respond_to?(:to_hash)
stack << [key, value.to_hash]
else
param << "#{key}=#{escape_value(value)}&"
end

stack.each do |parent, hash|
hash.each do |k, v|
if v.respond_to?(:to_hash)
stack << ["#{parent}[#{k}]", v.to_hash]
else
param << normalize_param("#{parent}[#{k}]", v)
end
end
end

param
end

# Prefer ERB::Util.url_encode, fall baack to deprecation URI.encode for
# old Ruby support
def self.escape_value(value)
if ERB::Util.respond_to? :url_encode
ERB::Util.url_encode(value.to_s)
else
URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
end
end
end
end
34 changes: 27 additions & 7 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ def json(data)
end
end

describe ".base_uri" do
it "should be set to customer.io's api" do
Customerio::Client.base_uri.should == "https://track.customer.io"
end
end

describe "#identify" do
it "sends a PUT request to customer.io's customer API" do
stub_request(:put, api_uri('/api/v1/customers/5')).
Expand Down Expand Up @@ -81,7 +75,7 @@ def json(data)

lambda { client.identify(:id => 5) }.should raise_error {|error|
error.should be_a Customerio::Client::InvalidResponse
error.response.code.should eq 500
error.response.code.should eq "500"
error.response.body.should eq "whatever"
}
end
Expand Down Expand Up @@ -162,6 +156,32 @@ def json(data)
client.track(5, "purchase", :type => "socks", :price => "13.99")
end

it "copes with arrays" do
stub_request(:post, api_uri('/api/v1/customers/5/events')).
with(:body => {
:name => "event",
:data => {
:things => ["a", "b", "c"]
}
}).
to_return(:status => 200, :body => "", :headers => {})

client.track(5, "event", :things => ["a", "b", "c"])
end

it "copes with hashes" do
stub_request(:post, api_uri('/api/v1/customers/5/events')).
with(:body => {
:name => "event",
:data => {
:stuff => { :a => "b" }
}
}).
to_return(:status => 200, :body => "", :headers => {})

client.track(5, "event", :stuff => { :a => "b" })
end

it "sends a POST request as json using json headers" do
client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true)
data = { :type => "socks", :price => "13.99" }
Expand Down