Skip to content

Commit

Permalink
Changed Mixpanel class to MixpanelClient to avoid naming collision in…
Browse files Browse the repository at this point in the history
… other libraries.
  • Loading branch information
keolo committed Mar 12, 2011
1 parent 972dfcd commit a710a84
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 114 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Ruby access to the [Mixpanel](http://mixpanel.com/) web analytics tool.
require 'rubygems'
require 'mixpanel_client'

client = Mixpanel::Client.new('api_key' => 'changeme', 'api_secret' => 'changeme')
client = MixpanelClient.new('api_key' => 'changeme', 'api_secret' => 'changeme')

data = client.request do
resource 'events/retention'
Expand All @@ -26,6 +26,12 @@ Ruby access to the [Mixpanel](http://mixpanel.com/) web analytics tool.

puts data.inspect

## Collaborators and Maintainers
[Keolo Keagy](http://github.com/keolo) (Author)
[Nathan Chong](http://github.com/paramaw)
[Paul McMahon](http://github.com/pwim)
[Chad Etzel](http://github.com/jazzychad)

## Copyright

Copyright (c) 2009+ Keolo Keagy. See LICENSE for details.
103 changes: 2 additions & 101 deletions lib/mixpanel_client.rb
Original file line number Diff line number Diff line change
@@ -1,101 +1,2 @@
#!/usr/bin/env ruby -Ku

# Mixpanel API Ruby Client Library
#
# Copyright (c) 2009+ Keolo Keagy
# See LICENSE for details.
#
# Inspired by the official mixpanel php and python libraries.
# http://mixpanel.com/api/docs/guides/api/

require 'cgi'
require 'digest/md5'
require 'open-uri'
require 'json' unless defined?(JSON)

# Ruby library for the mixpanel.com web service
module Mixpanel
BASE_URI = 'http://mixpanel.com/api'
VERSION = '2.0'

# The mixpanel client can be used to easily consume data through the mixpanel API
class Client
OPTIONS = [:resource, :event, :funnel, :name, :type, :unit, :interval, :limit, :format, :bucket]
attr_reader :uri
attr_accessor :api_key, :api_secret

OPTIONS.each do |option|
class_eval "
def #{option}(arg=nil)
arg ? @#{option} = arg : @#{option}
end
"
end

def initialize(config)
@api_key = config['api_key']
@api_secret = config['api_secret']
end

def params
OPTIONS.inject({}) do |params, param|
option = send(param)
params.merge!(param => option) if param != :resource && !option.nil?
params
end
end

def request(&options)
reset_options
instance_eval(&options)
@uri = URI.mixpanel(resource, normalize_params(params))
response = URI.get(@uri)
to_hash(response)
end

def normalize_params(params)
params.merge!(
:api_key => @api_key,
:expire => Time.now.to_i + 600 # Grant this request 10 minutes
).merge!(:sig => generate_signature(params))
end

def generate_signature(args)
Digest::MD5.hexdigest(args.map{|key,val| "#{key}=#{val}"}.sort.join + api_secret)
end

def to_hash(data)
if @format == 'csv'
data
else
JSON.parse(data)
end
end

def reset_options
(OPTIONS - [:resource]).each do |option|
eval "remove_instance_variable(:@#{option}) if defined?(@#{option})"
end
end
end

# URI related helpers
class URI
# Create an http error class for us to use
class HTTPError < StandardError; end

def self.mixpanel(resource, params)
File.join([BASE_URI, VERSION, resource.to_s]) + "?#{self.encode(params)}"
end

def self.encode(params)
params.map{|key,val| "#{key}=#{CGI.escape(val.to_s)}"}.sort.join('&')
end

def self.get(uri)
::URI.parse(uri).read
rescue OpenURI::HTTPError => error
raise HTTPError, JSON.parse(error.io.read)['error']
end
end
end
require "#{File.dirname(__FILE__)}/mixpanel_client/mixpanel_client"
require "#{File.dirname(__FILE__)}/mixpanel_client/mixpanel_client_uri"
79 changes: 79 additions & 0 deletions lib/mixpanel_client/mixpanel_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env ruby -Ku

# Mixpanel API Ruby Client Library
#
# Copyright (c) 2009+ Keolo Keagy
# See LICENSE for details.
#
# Inspired by the official mixpanel php and python libraries.
# http://mixpanel.com/api/docs/guides/api/

require 'cgi'
require 'digest/md5'
require 'open-uri'
require 'json' unless defined?(JSON)

# Ruby library for the mixpanel.com web service
class MixpanelClient
BASE_URI = 'http://mixpanel.com/api'
VERSION = '2.0'

# The mixpanel client can be used to easily consume data through the mixpanel API
OPTIONS = [:resource, :event, :funnel, :name, :type, :unit, :interval, :limit, :format, :bucket]
attr_reader :uri
attr_accessor :api_key, :api_secret

OPTIONS.each do |option|
class_eval "
def #{option}(arg=nil)
arg ? @#{option} = arg : @#{option}
end
"
end

def initialize(config)
@api_key = config['api_key']
@api_secret = config['api_secret']
end

def params
OPTIONS.inject({}) do |params, param|
option = send(param)
params.merge!(param => option) if param != :resource && !option.nil?
params
end
end

def request(&options)
reset_options
instance_eval(&options)
@uri = URI.mixpanel(resource, normalize_params(params))
response = URI.get(@uri)
to_hash(response)
end

def normalize_params(params)
params.merge!(
:api_key => @api_key,
:expire => Time.now.to_i + 600 # Grant this request 10 minutes
).merge!(:sig => generate_signature(params))
end

def generate_signature(args)
Digest::MD5.hexdigest(args.map{|key,val| "#{key}=#{val}"}.sort.join + api_secret)
end

def to_hash(data)
if @format == 'csv'
data
else
JSON.parse(data)
end
end

def reset_options
(OPTIONS - [:resource]).each do |option|
eval "remove_instance_variable(:@#{option}) if defined?(@#{option})"
end
end
end
29 changes: 29 additions & 0 deletions lib/mixpanel_client/mixpanel_client_uri.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env ruby -Ku

# Mixpanel API Ruby Client Library
#
# Copyright (c) 2009+ Keolo Keagy
# See LICENSE for details.
#
# Inspired by the official mixpanel php and python libraries.
# http://mixpanel.com/api/docs/guides/api/

# URI related helpers
class MixpanelClient::URI
# Create an http error class for us to use
class HTTPError < StandardError; end

def self.mixpanel(resource, params)
File.join([MixpanelClient::BASE_URI, MixpanelClient::VERSION, resource.to_s]) + "?#{self.encode(params)}"
end

def self.encode(params)
params.map{|key,val| "#{key}=#{CGI.escape(val.to_s)}"}.sort.join('&')
end

def self.get(uri)
::URI.parse(uri).read
rescue OpenURI::HTTPError => error
raise HTTPError, JSON.parse(error.io.read)['error']
end
end
4 changes: 2 additions & 2 deletions spec/events_externalspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
before :all do
config = YAML.load_file(File.dirname(__FILE__) + '/../config/mixpanel.yml')
config.should_not be_nil
@client = Mixpanel::Client.new(config)
@client = MixpanelClient.new(config)
end

describe 'Events' do
Expand All @@ -17,7 +17,7 @@
resource 'events'
end
}
data.should raise_error(Mixpanel::URI::HTTPError)
data.should raise_error(MixpanelClient::URI::HTTPError)
end

it 'should return events' do
Expand Down
20 changes: 10 additions & 10 deletions spec/mixpanel_client_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
require 'rubygems'
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe Mixpanel::Client do
describe MixpanelClient do
before :all do
config = {'api_key' => 'test', 'api_secret' => 'test'}
@client = Mixpanel::Client.new(config)
@uri = Regexp.escape(Mixpanel::BASE_URI)
@client = MixpanelClient.new(config)
@uri = Regexp.escape(MixpanelClient::BASE_URI)
end

describe '#request' do
Expand Down Expand Up @@ -96,44 +96,44 @@
bucket 'list'
end

Mixpanel::Client::OPTIONS.each do |option|
MixpanelClient::OPTIONS.each do |option|
@client.send(option).should_not be_nil
end

@client.request do
resource 'events/properties/top'
end

(Mixpanel::Client::OPTIONS - [:resource]).each do |option|
(MixpanelClient::OPTIONS - [:resource]).each do |option|
@client.send(option).should be_nil
end
end
end
end

describe Mixpanel::URI do
describe MixpanelClient::URI do
describe '.mixpanel' do
it 'should return a properly formatted mixpanel uri as a string (without an endpoint)' do
resource, params = ['events', {:c => 'see', :a => 'aye'}]
Mixpanel::URI.mixpanel(resource, params).should == 'http://mixpanel.com/api/2.0/events?a=aye&c=see'
MixpanelClient::URI.mixpanel(resource, params).should == 'http://mixpanel.com/api/2.0/events?a=aye&c=see'
end
it 'should return a properly formatted mixpanel uri as a string (with an endpoint)' do
resource, params = ['events/top', {:c => 'see', :a => 'aye'}]
Mixpanel::URI.mixpanel(resource, params).should == 'http://mixpanel.com/api/2.0/events/top?a=aye&c=see'
MixpanelClient::URI.mixpanel(resource, params).should == 'http://mixpanel.com/api/2.0/events/top?a=aye&c=see'
end
end

describe '.encode' do
it 'should return a string with url encoded values.' do
params = {:hey => '!@#$%^&*()\/"Ü', :soo => "hëllö?"}
Mixpanel::URI.encode(params).should == 'hey=%21%40%23%24%25%5E%26%2A%28%29%5C%2F%22%C3%9C&soo=h%C3%ABll%C3%B6%3F'
MixpanelClient::URI.encode(params).should == 'hey=%21%40%23%24%25%5E%26%2A%28%29%5C%2F%22%C3%9C&soo=h%C3%ABll%C3%B6%3F'
end
end

describe '.get' do
it 'should return a string response' do
stub_request(:get, 'http://example.com').to_return(:body => 'something')
Mixpanel::URI.get('http://example.com').should == 'something'
MixpanelClient::URI.get('http://example.com').should == 'something'
end
end
end

0 comments on commit a710a84

Please sign in to comment.