analytics-ruby 1.1.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock ADDED
@@ -0,0 +1,47 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ analytics-ruby (2.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ predicated (0.2.6)
11
+ rake (10.3.1)
12
+ rspec (2.14.1)
13
+ rspec-core (~> 2.14.0)
14
+ rspec-expectations (~> 2.14.0)
15
+ rspec-mocks (~> 2.14.0)
16
+ rspec-core (2.14.8)
17
+ rspec-expectations (2.14.5)
18
+ diff-lcs (>= 1.1.3, < 2.0)
19
+ rspec-mocks (2.14.6)
20
+ ruby2ruby (2.1.0)
21
+ ruby_parser (~> 3.1)
22
+ sexp_processor (~> 4.0)
23
+ ruby_parser (3.6.1)
24
+ sexp_processor (~> 4.1)
25
+ sexp_processor (4.4.3)
26
+ wrong (0.7.1)
27
+ diff-lcs (~> 1.2.5)
28
+ predicated (~> 0.2.6)
29
+ ruby2ruby (>= 2.0.1)
30
+ ruby_parser (>= 3.0.1)
31
+ sexp_processor (>= 4.0)
32
+ wrong (0.7.1-java)
33
+ diff-lcs (~> 1.2.5)
34
+ predicated (~> 0.2.6)
35
+ ruby2ruby (>= 2.0.1)
36
+ ruby_parser (>= 3.0.1)
37
+ sexp_processor (>= 4.0)
38
+
39
+ PLATFORMS
40
+ java
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ analytics-ruby!
45
+ rake (~> 10.3)
46
+ rspec (~> 2.0)
47
+ wrong (~> 0.0)
@@ -1,11 +1,8 @@
1
- $:.push File.expand_path('../lib', __FILE__)
2
-
3
- require 'analytics-ruby/version'
4
-
1
+ require File.expand_path('../lib/segment/analytics/version', __FILE__)
5
2
 
6
3
  Gem::Specification.new do |spec|
7
4
  spec.name = 'analytics-ruby'
8
- spec.version = AnalyticsRuby::VERSION
5
+ spec.version = Segment::Analytics::VERSION
9
6
  spec.files = Dir.glob('**/*')
10
7
  spec.require_paths = ['lib']
11
8
  spec.summary = 'Segment.io analytics library'
@@ -18,6 +15,7 @@ Gem::Specification.new do |spec|
18
15
  # Ruby 1.8 requires json
19
16
  spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9"
20
17
 
21
- spec.add_development_dependency('rake')
22
- spec.add_development_dependency('rspec')
18
+ spec.add_development_dependency 'rake', '~> 10.3'
19
+ spec.add_development_dependency 'wrong', '~> 0.0'
20
+ spec.add_development_dependency 'rspec', '~> 2.0'
23
21
  end
data/lib/segment.rb ADDED
@@ -0,0 +1 @@
1
+ require 'segment/analytics'
@@ -0,0 +1,31 @@
1
+ require 'segment/analytics/defaults'
2
+ require 'segment/analytics/utils'
3
+ require 'segment/analytics/version'
4
+ require 'segment/analytics/client'
5
+ require 'segment/analytics/worker'
6
+ require 'segment/analytics/request'
7
+ require 'segment/analytics/response'
8
+ require 'segment/analytics/logging'
9
+
10
+ module Segment
11
+ class Analytics
12
+ def initialize options = {}
13
+ Request.stub = options[:stub]
14
+ @client = Segment::Analytics::Client.new options
15
+ end
16
+
17
+ def method_missing message, *args, &block
18
+ if @client.respond_to? message
19
+ @client.send message, *args, &block
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def respond_to? method_name, include_private = false
26
+ @client.respond_to?(method_missing) || super
27
+ end
28
+
29
+ include Logging
30
+ end
31
+ end
@@ -0,0 +1,335 @@
1
+ require 'thread'
2
+ require 'time'
3
+ require 'segment/analytics/utils'
4
+ require 'segment/analytics/worker'
5
+ require 'segment/analytics/defaults'
6
+
7
+ module Segment
8
+ class Analytics
9
+ class Client
10
+ include Segment::Analytics::Utils
11
+
12
+ # public: Creates a new client
13
+ #
14
+ # options - Hash
15
+ # :write_key - String of your project's write_key
16
+ # :max_queue_size - Fixnum of the max calls to remain queued (optional)
17
+ # :on_error - Proc which handles error calls from the API
18
+ def initialize options = {}
19
+ symbolize_keys! options
20
+
21
+ @queue = Queue.new
22
+ @write_key = options[:write_key]
23
+ @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE
24
+ @options = options
25
+ @worker_mutex = Mutex.new
26
+
27
+ check_write_key!
28
+
29
+ at_exit { @worker_thread && @worker_thread[:should_exit] = true }
30
+ end
31
+
32
+ # public: Synchronously waits until the worker has flushed the queue.
33
+ # Use only for scripts which are not long-running, and will
34
+ # specifically exit
35
+ #
36
+ def flush
37
+ while !@queue.empty? || @worker.is_requesting?
38
+ ensure_worker_running
39
+ sleep(0.1)
40
+ end
41
+ end
42
+
43
+ # public: Tracks an event
44
+ #
45
+ # options - Hash
46
+ # :event - String of event name.
47
+ # :user_id - String of the user id.
48
+ # :properties - Hash of event properties. (optional)
49
+ # :timestamp - Time of when the event occurred. (optional)
50
+ # :context - Hash of context. (optional)
51
+ def track options
52
+ symbolize_keys! options
53
+ check_user_id! options
54
+
55
+ event = options[:event]
56
+ properties = options[:properties] || {}
57
+ timestamp = options[:timestamp] || Time.new
58
+ context = options[:context] || {}
59
+
60
+ check_timestamp! timestamp
61
+
62
+ if event.nil? || event.empty?
63
+ fail ArgumentError, 'Must supply event as a non-empty string'
64
+ end
65
+
66
+ fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash
67
+ isoify_dates! properties
68
+
69
+ add_context context
70
+
71
+ enqueue({
72
+ :event => event,
73
+ :userId => options[:user_id],
74
+ :anonymousId => options[:anonymous_id],
75
+ :context => context,
76
+ :integrations => options[:integrations],
77
+ :properties => properties,
78
+ :timestamp => datetime_in_iso8601(timestamp),
79
+ :type => 'track'
80
+ })
81
+ end
82
+
83
+ # public: Identifies a user
84
+ #
85
+ # options - Hash
86
+ # :user_id - String of the user id
87
+ # :traits - Hash of user traits. (optional)
88
+ # :timestamp - Time of when the event occurred. (optional)
89
+ # :context - Hash of context. (optional)
90
+ def identify options
91
+ symbolize_keys! options
92
+ check_user_id! options
93
+
94
+ traits = options[:traits] || {}
95
+ timestamp = options[:timestamp] || Time.new
96
+ context = options[:context] || {}
97
+
98
+ check_timestamp! timestamp
99
+
100
+ fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash
101
+ isoify_dates! traits
102
+
103
+ add_context context
104
+
105
+ enqueue({
106
+ :userId => options[:user_id],
107
+ :anonymousId => options[:anonymous_id],
108
+ :integrations => options[:integrations],
109
+ :context => context,
110
+ :traits => traits,
111
+ :timestamp => datetime_in_iso8601(timestamp),
112
+ :type => 'identify'
113
+ })
114
+ end
115
+
116
+ # public: Aliases a user from one id to another
117
+ #
118
+ # options - Hash
119
+ # :previous_id - String of the id to alias from
120
+ # :user_id - String of the id to alias to
121
+ # :timestamp - Time of when the alias occured (optional)
122
+ # :context - Hash of context (optional)
123
+ def alias(options)
124
+ symbolize_keys! options
125
+
126
+ from = options[:previous_id]
127
+ to = options[:user_id]
128
+ timestamp = options[:timestamp] || Time.new
129
+ context = options[:context] || {}
130
+
131
+ check_presence! from, 'previous_id'
132
+ check_presence! to, 'user_id'
133
+ check_timestamp! timestamp
134
+ add_context context
135
+
136
+ enqueue({
137
+ :previousId => from,
138
+ :userId => to,
139
+ :integrations => options[:integrations],
140
+ :context => context,
141
+ :timestamp => datetime_in_iso8601(timestamp),
142
+ :type => 'alias'
143
+ })
144
+ end
145
+
146
+ # public: Associates a user identity with a group.
147
+ #
148
+ # options - Hash
149
+ # :previous_id - String of the id to alias from
150
+ # :user_id - String of the id to alias to
151
+ # :timestamp - Time of when the alias occured (optional)
152
+ # :context - Hash of context (optional)
153
+ def group(options)
154
+ symbolize_keys! options
155
+ check_user_id! options
156
+
157
+ group_id = options[:group_id]
158
+ user_id = options[:user_id]
159
+ traits = options[:traits] || {}
160
+ timestamp = options[:timestamp] || Time.new
161
+ context = options[:context] || {}
162
+
163
+ fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash
164
+
165
+ check_presence! group_id, 'group_id'
166
+ check_timestamp! timestamp
167
+ add_context context
168
+
169
+ enqueue({
170
+ :groupId => group_id,
171
+ :userId => user_id,
172
+ :traits => traits,
173
+ :integrations => options[:integrations],
174
+ :context => context,
175
+ :timestamp => datetime_in_iso8601(timestamp),
176
+ :type => 'group'
177
+ })
178
+ end
179
+
180
+ # public: Records a page view
181
+ #
182
+ # options - Hash
183
+ # :user_id - String of the id to alias from
184
+ # :name - String name of the page
185
+ # :properties - Hash of page properties (optional)
186
+ # :timestamp - Time of when the pageview occured (optional)
187
+ # :context - Hash of context (optional)
188
+ def page(options)
189
+ symbolize_keys! options
190
+ check_user_id! options
191
+
192
+ name = options[:name].to_s
193
+ properties = options[:properties] || {}
194
+ timestamp = options[:timestamp] || Time.new
195
+ context = options[:context] || {}
196
+
197
+ fail ArgumentError, '.name must be a string' unless !name.empty?
198
+ fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash
199
+ isoify_dates! properties
200
+
201
+ check_timestamp! timestamp
202
+ add_context context
203
+
204
+ enqueue({
205
+ :userId => options[:user_id],
206
+ :anonymousId => options[:anonymous_id],
207
+ :name => name,
208
+ :properties => properties,
209
+ :integrations => options[:integrations],
210
+ :context => context,
211
+ :timestamp => datetime_in_iso8601(timestamp),
212
+ :type => 'page'
213
+ })
214
+ end
215
+ # public: Records a screen view (for a mobile app)
216
+ #
217
+ # options - Hash
218
+ # :user_id - String of the id to alias from
219
+ # :name - String name of the screen
220
+ # :properties - Hash of screen properties (optional)
221
+ # :timestamp - Time of when the screen occured (optional)
222
+ # :context - Hash of context (optional)
223
+ def screen(options)
224
+ symbolize_keys! options
225
+ check_user_id! options
226
+
227
+ name = options[:name].to_s
228
+ properties = options[:properties] || {}
229
+ timestamp = options[:timestamp] || Time.new
230
+ context = options[:context] || {}
231
+
232
+ fail ArgumentError, '.name must be a string' if name.empty?
233
+ fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash
234
+ isoify_dates! properties
235
+
236
+ check_timestamp! timestamp
237
+ add_context context
238
+
239
+ enqueue({
240
+ :userId => options[:user_id],
241
+ :anonymousId => options[:anonymous_id],
242
+ :name => name,
243
+ :properties => properties,
244
+ :integrations => options[:integrations],
245
+ :context => context,
246
+ :timestamp => timestamp.iso8601,
247
+ :type => 'screen'
248
+ })
249
+ end
250
+
251
+ # public: Returns the number of queued messages
252
+ #
253
+ # returns Fixnum of messages in the queue
254
+ def queued_messages
255
+ @queue.length
256
+ end
257
+
258
+ private
259
+
260
+ # private: Enqueues the action.
261
+ #
262
+ # returns Boolean of whether the item was added to the queue.
263
+ def enqueue(action)
264
+ # add our request id for tracing purposes
265
+ action[:messageId] = uid
266
+ unless queue_full = @queue.length >= @max_queue_size
267
+ ensure_worker_running
268
+ @queue << action
269
+ end
270
+ !queue_full
271
+ end
272
+
273
+ # private: Ensures that a string is non-empty
274
+ #
275
+ # obj - String|Number that must be non-blank
276
+ # name - Name of the validated value
277
+ #
278
+ def check_presence!(obj, name)
279
+ if obj.nil? || (obj.is_a?(String) && obj.empty?)
280
+ fail ArgumentError, "#{name} must be given"
281
+ end
282
+ end
283
+
284
+ # private: Adds contextual information to the call
285
+ #
286
+ # context - Hash of call context
287
+ def add_context(context)
288
+ context[:library] = { :name => "analytics-ruby", :version => Segment::Analytics::VERSION.to_s }
289
+ end
290
+
291
+ # private: Checks that the write_key is properly initialized
292
+ def check_write_key!
293
+ fail ArgumentError, 'Write key must be initialized' if @write_key.nil?
294
+ end
295
+
296
+ # private: Checks the timstamp option to make sure it is a Time.
297
+ def check_timestamp!(timestamp)
298
+ fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
299
+ end
300
+
301
+ def event attrs
302
+ symbolize_keys! attrs
303
+
304
+ {
305
+ :userId => user_id,
306
+ :name => name,
307
+ :properties => properties,
308
+ :context => context,
309
+ :timestamp => datetime_in_iso8601(timestamp),
310
+ :type => 'screen'
311
+ }
312
+ end
313
+
314
+ def check_user_id! options
315
+ fail ArgumentError, 'Must supply either user_id or anonymous_id' unless options[:user_id] || options[:anonymous_id]
316
+ end
317
+
318
+ def ensure_worker_running
319
+ return if worker_running?
320
+ @worker_mutex.synchronize do
321
+ return if worker_running?
322
+ @worker_thread = Thread.new do
323
+ @worker = Worker.new @queue, @write_key, @options
324
+ @worker.run
325
+ end
326
+ end
327
+ end
328
+
329
+ def worker_running?
330
+ @worker_thread && @worker_thread.alive?
331
+ end
332
+ end
333
+ end
334
+ end
335
+
@@ -0,0 +1,20 @@
1
+ module Segment
2
+ class Analytics
3
+ module Defaults
4
+ module Request
5
+ HOST = 'api.segment.io'
6
+ PORT = 443
7
+ PATH = '/v1/import'
8
+ SSL = true
9
+ HEADERS = { :accept => 'application/json' }
10
+ RETRIES = 4
11
+ BACKOFF = 30.0
12
+ end
13
+
14
+ module Queue
15
+ BATCH_SIZE = 100
16
+ MAX_SIZE = 10000
17
+ end
18
+ end
19
+ end
20
+ end