tamber 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Create
4
+ def create(params={})
5
+ response = request(:post, url+"/create", params)
6
+ Util.convert_to_tamber_object(response)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Remove
4
+ def remove(params={})
5
+ response = request(:get, url+"/remove", params)
6
+ # initialize_from(response, opts)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Request
4
+ module ClassMethods
5
+ def request(method, url, params={})
6
+ response = Tamber.request(method, url, params)
7
+ end
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ protected
15
+
16
+ def request(method, url, params={})
17
+ self.class.request(method, url, params)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Retrieve
4
+ def retrieve(params={})
5
+ response = request(:get, url+"/retrieve", params)
6
+ Util.convert_to_tamber_object(response)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Update
4
+ def update(params={})
5
+ response = request(:post, url+"/update", params)
6
+ Util.convert_to_tamber_object(response)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module Tamber
2
+ class APIResource < TamberObject
3
+ include Tamber::APIOperations::Request
4
+
5
+ def self.class_name
6
+ self.name.split('::')[-1]
7
+ end
8
+
9
+ def self.url
10
+ if self == APIResource
11
+ raise TamberError.new('APIResource is an abstract class. You should perform actions on its subclasses (Event, Discover, etc.)')
12
+ end
13
+ "/#{CGI.escape(class_name.downcase)}"
14
+ end
15
+
16
+ def url
17
+ "#{self.class.url}"
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module Tamber
2
+ class Behavior < APIResource
3
+ extend Tamber::APIOperations::Create
4
+ extend Tamber::APIOperations::Retrieve
5
+ end
6
+ end
@@ -0,0 +1,81 @@
1
+ module Tamber
2
+ class Discover < APIResource
3
+
4
+ def self.next(params={})
5
+ response = request(:get, self.next_url, params)
6
+ Util.convert_to_tamber_object(response)
7
+ end
8
+
9
+ def self.recommended(params={})
10
+ response = request(:get, self.recommended_url, params)
11
+ Util.convert_to_tamber_object(response)
12
+ end
13
+
14
+ def self.similar(params={})
15
+ response = request(:get, self.similar_url, params)
16
+ Util.convert_to_tamber_object(response)
17
+ end
18
+
19
+ def self.recommendedSimilar(params={})
20
+ response = request(:get, self.recommendedSimilar_url, params)
21
+ Util.convert_to_tamber_object(response)
22
+ end
23
+
24
+ def self.popular(params={})
25
+ response = request(:get, self.popular_url, params)
26
+ Util.convert_to_tamber_object(response)
27
+ end
28
+
29
+ def self.hot(params={})
30
+ response = request(:get, self.hot_url, params)
31
+ Util.convert_to_tamber_object(response)
32
+ end
33
+
34
+ def self.uac(params={})
35
+ response = request(:get, self.uac_url, params)
36
+ Util.convert_to_tamber_object(response)
37
+ end
38
+
39
+ def self.new(params={})
40
+ response = request(:get, self.new_url, params)
41
+ Util.convert_to_tamber_object(response)
42
+ end
43
+
44
+ def self.recommended_url
45
+ url + '/recommended'
46
+ end
47
+
48
+ def self.similar_url
49
+ url + '/similar'
50
+ end
51
+
52
+ def self.recommendedSimilar_url
53
+ url + '/recommended_similar'
54
+ end
55
+
56
+ def self.next_url
57
+ url + '/next'
58
+ end
59
+
60
+ def self.popular_url
61
+ url + '/popular'
62
+ end
63
+
64
+ def self.hot_url
65
+ url + '/hot'
66
+ end
67
+
68
+ def self.uac_url
69
+ url + '/uac'
70
+ end
71
+
72
+ def self.new_url
73
+ url + '/new'
74
+ end
75
+
76
+ end
77
+
78
+ # Discover requests return an array of 'discovery' objects
79
+ class Discovery < TamberObject
80
+ end
81
+ end
@@ -0,0 +1,24 @@
1
+ module Tamber
2
+ class Event < APIResource
3
+
4
+ extend Tamber::APIOperations::Retrieve
5
+
6
+ def self.track_url
7
+ url + '/track'
8
+ end
9
+
10
+ def self.batch_url
11
+ url + '/batch'
12
+ end
13
+
14
+ def self.track(params={})
15
+ response = request(:post, self.track_url, params)
16
+ Util.convert_to_tamber_object(response)
17
+ end
18
+
19
+ def self.batch(params={})
20
+ response = request(:post, self.batch_url, params)
21
+ Util.convert_to_tamber_object(response)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ module Tamber
2
+ class Item < APIResource
3
+ extend Tamber::APIOperations::Create
4
+ extend Tamber::APIOperations::Update
5
+ extend Tamber::APIOperations::Retrieve
6
+
7
+ # hide an item from all Discover results
8
+ def self.hide(params={})
9
+ response = request(:post, self.hide_url, params)
10
+ Util.convert_to_tamber_object(response)
11
+ end
12
+
13
+ # un-hide an item from all Discover results
14
+ def self.unhide(params={})
15
+ response = request(:post, self.unhide_url, params)
16
+ Util.convert_to_tamber_object(response)
17
+ end
18
+
19
+ # permenantly delete an item and all associated events
20
+ def self.delete(params={})
21
+ response = request(:post, self.delete_url, params)
22
+ Util.convert_to_tamber_object(response)
23
+ end
24
+
25
+ def self.hide_url
26
+ url + '/hide'
27
+ end
28
+
29
+ def self.unhide_url
30
+ url + '/unhide'
31
+ end
32
+
33
+ def self.delete_url
34
+ url + '/delete'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ class TamberError < StandardError
3
+ attr_reader :message
4
+
5
+ def initialize(message=nil)
6
+ @message = message
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ module Tamber
2
+ class TamberObject
3
+
4
+ def initialize(values={})
5
+ values.each do |k,v|
6
+ val = self.class.curvert(v)
7
+ self.instance_variable_set("@#{k}", val)
8
+ self.singleton_class.send(:attr_accessor, k)
9
+ end
10
+ end
11
+
12
+ def self.construct_from(values)
13
+ values = Tamber::Util.symbolize_names(values)
14
+ self.new(values)
15
+ end
16
+
17
+ def self.curvert(values={})
18
+ case values
19
+ when Hash
20
+ if values.has_key?(:object)
21
+ Tamber::Util.object_classes.fetch(values[:object],TamberObject).new(values)
22
+ else
23
+ values.each do |k,v|
24
+ values[k] = curvert(v)
25
+ end
26
+ end
27
+ when Array
28
+ values.map { |i| curvert(i) }
29
+ else
30
+ values
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ module Tamber
2
+ class User < APIResource
3
+ extend Tamber::APIOperations::Create
4
+ extend Tamber::APIOperations::Update
5
+ extend Tamber::APIOperations::Retrieve
6
+
7
+ def self.search(params={})
8
+ response = request(:post, url + '/search', params)
9
+ Util.convert_to_tamber_object(response)
10
+ end
11
+
12
+ def self.merge(params={})
13
+ response = request(:post, url + '/merge', params)
14
+ Util.convert_to_tamber_object(response)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,89 @@
1
+ require "cgi"
2
+ require "json"
3
+
4
+ module Tamber
5
+ module Util
6
+
7
+ def self.encode_parameters(params)
8
+ Util.flatten_params(params).
9
+ map { |k,v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
10
+ end
11
+
12
+ def self.url_encode(key)
13
+ CGI.escape(key.to_s).
14
+ gsub('%5B', '[').gsub('%5D', ']')
15
+ end
16
+
17
+ def self.flatten_params(params, parent_key=nil)
18
+ result = []
19
+
20
+ params.sort_by { |(k, v)| k.to_s }.each do |key, value|
21
+ calculated_key = parent_key ? "#{parent_key}[#{key}]" : "#{key}"
22
+ if value.is_a?(Hash) || value.is_a?(Array) || value.is_a?(APIResource)
23
+ result << [calculated_key, value.to_json]
24
+ else
25
+ result << [calculated_key, value]
26
+ end
27
+ end
28
+
29
+ result
30
+ end
31
+
32
+ def self.object_classes
33
+ @object_classes ||= {
34
+ # Core
35
+ 'event' => Event,
36
+ 'discovery' => Discovery,
37
+
38
+ # Expanded
39
+ 'user' => User,
40
+ 'item' => Item,
41
+ 'behavior' => Behavior,
42
+ }
43
+ end
44
+
45
+ def self.convert_full(resp)
46
+ case resp
47
+ when Hash
48
+ object_classes.fetch(resp[:object],TamberObject).construct_from(resp)
49
+ when Array
50
+ resp.map { |i| convert_full(i) }
51
+ else
52
+ resp
53
+ end
54
+ end
55
+
56
+ def self.convert_to_tamber_object(resp)
57
+ case resp
58
+ when Hash
59
+ object_classes.fetch(resp[:object],TamberObject).construct_from(resp)
60
+ when Array
61
+ if resp.size == 1
62
+ resp = resp[0]
63
+ self.convert_full(resp)
64
+ else
65
+ resp.map { |i| convert_full(i) }
66
+ end
67
+ else
68
+ resp
69
+ end
70
+ end
71
+
72
+ def self.symbolize_names(object)
73
+ case object
74
+ when Hash
75
+ new_hash = {}
76
+ object.each do |key, value|
77
+ key = (key.to_sym rescue key) || key
78
+ new_hash[key] = symbolize_names(value)
79
+ end
80
+ new_hash
81
+ when Array
82
+ object.map { |value| symbolize_names(value) }
83
+ else
84
+ object
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module Tamber
2
+ VERSION = '0.1.7'
3
+ end
data/lib/tamber.rb ADDED
@@ -0,0 +1,202 @@
1
+ # Tamber Ruby bindings
2
+ # API spec at https://tamber.com/docs/api
3
+ require 'cgi'
4
+ require 'openssl'
5
+ require 'rbconfig'
6
+ require 'set'
7
+ require 'socket'
8
+ require 'base64'
9
+
10
+ require 'rest-client'
11
+ require 'json'
12
+
13
+ require 'tamber/version'
14
+
15
+ require 'tamber/api_operations/create'
16
+ require 'tamber/api_operations/update'
17
+ require 'tamber/api_operations/remove'
18
+ require 'tamber/api_operations/retrieve'
19
+ require 'tamber/api_operations/request'
20
+
21
+ require 'tamber/tamber_object'
22
+ require 'tamber/util'
23
+ require 'tamber/api_resource'
24
+ require 'tamber/discover'
25
+ require 'tamber/event'
26
+ require 'tamber/user'
27
+ require 'tamber/item'
28
+ require 'tamber/behavior'
29
+
30
+
31
+ require 'tamber/tamber_error'
32
+
33
+
34
+ module Tamber
35
+ DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/security/ca-bundle.crt'
36
+
37
+ @ca_bundle_path = DEFAULT_CA_BUNDLE_PATH
38
+ @verify_ssl_certs = true
39
+
40
+ @api_url = 'https://api.tamber.com/v1'
41
+ @open_timeout = 30
42
+ @read_timeout = 80
43
+
44
+ class << self
45
+ attr_accessor :project_key, :engine_key, :api_version, :verify_ssl_certs, :open_timeout, :read_timeout
46
+ end
47
+
48
+
49
+ def self.api_url(api_base_url=nil, url='')
50
+ (api_base_url || @api_url) + url
51
+ end
52
+
53
+ def self.ca_bundle_path
54
+ @ca_bundle_path
55
+ end
56
+
57
+ def self.ca_bundle_path=(path)
58
+ @ca_bundle_path = path
59
+ end
60
+
61
+ def self.request(method, url, params={})
62
+
63
+ if project_key =~ /\s/
64
+ raise TamberError.new('Your project key is invalid, as it contains ' \
65
+ 'whitespace. (HINT: You can double-check your project key from the ' \
66
+ 'Tamber dashboard. See https://dashboard.tamber.com to get your engine\'s key, or ' \
67
+ 'email support@tamber.com if you have any questions.)')
68
+ end
69
+
70
+ if engine_key =~ /\s/
71
+ raise TamberError.new('Your engine key is invalid, as it contains ' \
72
+ 'whitespace. (HINT: You can double-check your project key from the ' \
73
+ 'Tamber dashboard. See https://dashboard.tamber.com to get your engine\'s key, or ' \
74
+ 'email support@tamber.com if you have any questions.)')
75
+ end
76
+
77
+ if verify_ssl_certs
78
+ request_opts = {:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
79
+ :ssl_ca_file => @ca_bundle_path}
80
+ else
81
+ request_opts = {:verify_ssl => false}
82
+ unless @verify_ssl_warned
83
+ @verify_ssl_warned = true
84
+ $stderr.puts("WARNING: Running without SSL cert verification. " \
85
+ "You should never do this in production. " \
86
+ "Execute 'Tamber.verify_ssl_certs = true' to enable verification.")
87
+ end
88
+ end
89
+
90
+ url = @api_url + url
91
+
92
+ case method.to_s.downcase.to_sym
93
+ when :get, :head, :delete
94
+ # Make params into GET parameters
95
+ url += "#{URI.parse(url).query ? '&' : '?'}#{Util.encode_parameters(params)}" if params && params.any?
96
+ payload = nil
97
+ else
98
+ payload = Util.encode_parameters(params)
99
+ end
100
+
101
+ request_opts.update(:headers => request_headers(project_key, engine_key, method),
102
+ :method => method, :open_timeout => open_timeout,
103
+ :payload => payload, :url => url, :timeout => read_timeout)
104
+
105
+ response = execute_request_with_rescues(request_opts)
106
+
107
+ [parse(response)]
108
+ end
109
+
110
+
111
+
112
+ def self.request_headers(pkey, ekey, method)
113
+ pkey ||= ''
114
+ ekey ||= ''
115
+ encoded_key = Base64.encode64(pkey + ':' + ekey)
116
+ headers = {
117
+ :user_agent => "Tamber/v1 RubyBindings/#{Tamber::VERSION}",
118
+ :authorization => "Basic "+encoded_key,
119
+ :content_type => 'application/x-www-form-urlencoded'
120
+ }
121
+
122
+ headers[:tamber_version] = api_version if api_version
123
+
124
+ begin
125
+ headers.update(:x_tamber_client_user_agent => JSON.generate(user_agent))
126
+ rescue => e
127
+ headers.update(:x_tamber_client_raw_user_agent => user_agent.inspect,
128
+ :error => "#{e} (#{e.class})")
129
+ end
130
+ end
131
+
132
+ def self.user_agent
133
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
134
+
135
+ {
136
+ :bindings_version => Tamber::VERSION,
137
+ :lang => 'ruby',
138
+ :lang_version => lang_version,
139
+ :platform => RUBY_PLATFORM,
140
+ :engine => defined?(RUBY_ENGINE) ? RUBY_ENGINE : '',
141
+ :publisher => 'tamber',
142
+ :hostname => Socket.gethostname,
143
+ }
144
+
145
+ end
146
+
147
+ def self.general_api_error(rbody)
148
+ TamberError.new("Invalid response object from API: "+rbody.inspect)
149
+ end
150
+
151
+ def self.handle_restclient_error(e, request_opts)
152
+ parse(e)
153
+ end
154
+
155
+ def self.execute_request(opts)
156
+ RestClient::Request.execute(opts)
157
+ end
158
+
159
+ def self.parse(response)
160
+ begin
161
+ response = JSON.parse(response.body)
162
+ if response["success"]
163
+ response = response["result"]
164
+ else
165
+ raise TamberError.new("Error: "+response["error"])
166
+ end
167
+ rescue JSON::ParserError
168
+ raise general_api_error(response.body)
169
+ end
170
+ Util.symbolize_names(response)
171
+ end
172
+
173
+ private
174
+
175
+ def self.execute_request_with_rescues(request_opts)
176
+ # response = execute_request(request_opts)
177
+ begin
178
+ response = execute_request(request_opts)
179
+ rescue SocketError => e
180
+ response = handle_restclient_error(e, request_opts)
181
+ rescue NoMethodError => e
182
+ # Work around RestClient bug
183
+ if e.message =~ /\WRequestFailed\W/
184
+ raise TamberError.new('Error: Unexpected HTTP response code')
185
+ else
186
+ raise
187
+ end
188
+ rescue RestClient::ExceptionWithResponse => e
189
+ puts "ExceptionWithResponse: #{e}"
190
+ if e.response
191
+ puts "e.response"
192
+ parse(e.response)
193
+ else
194
+ response = handle_restclient_error(e, request_opts)
195
+ end
196
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
197
+ response = handle_restclient_error(e, request_opts)
198
+ end
199
+
200
+ response
201
+ end
202
+ end
data/tamber.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'tamber/version'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.name = 'tamber'
7
+ s.version = Tamber::VERSION
8
+ s.required_ruby_version = '>= 1.9.3'
9
+ s.summary = 'Ruby bindings for the Tamber API'
10
+ s.description = 'Tamber is the easiest way to put head-scratchingly accurate, real time recommendations in your app. See https://tamber.com for details.'
11
+ s.homepage = 'https://tamber.com/docs/api'
12
+ s.license = 'MIT'
13
+ s.authors = ['Alexi Robbins', 'Mark Canning']
14
+ s.email = ['alexi@tamber.com', 'argusdusty@tamber.com']
15
+
16
+ s.add_dependency('rest-client', '~> 1.4')
17
+ s.add_dependency('json', '~> 1.8.1')
18
+ s.add_dependency('activesupport', '~> 4.2.4')
19
+
20
+ s.add_development_dependency('mocha', '~> 0.13.2')
21
+ s.add_development_dependency('shoulda', '~> 3.4.0')
22
+ s.add_development_dependency('test-unit')
23
+ s.add_development_dependency('rake')
24
+
25
+ # if Gem::Version.new(RUBY_VERSION.dup) > Gem::Version.new('2.0.0')
26
+ # s.add_development_dependency("byebug")
27
+ # s.add_development_dependency("pry")
28
+ # s.add_development_dependency("pry-byebug")
29
+ # end
30
+
31
+ s.files = `git ls-files`.split("\n")
32
+ s.test_files = `git ls-files -- test/*`.split("\n")
33
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
34
+ s.require_paths = ['lib']
35
+ end