meta_events 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDEzNzUyNjM1MmVhOGM1YmRhODUzYTI3Njg0ZDk4Mjc3MDY3OWFjYQ==
4
+ NzJiMzIzNTY1ZGVhZTQwZjFmYWE1YWM5NTY3YzRhYjMyNDMyZDgwYw==
5
5
  data.tar.gz: !binary |-
6
- NjE4N2Q4MDc1YjFmNmM0MDUxZDEwYzA5NzQ5ODY2ZjNlMDQ1NTgyMA==
6
+ Y2FkY2IxOTdhNTFmODVhMTQ1YTk1ODUxNzViNDIxNGNkOGFhMTk5ZQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NGY3ZGFkYzU5MjE0MmI0YjZhNDRmZGRhMjM3NWM4MmJjNmNmZDlmMGY1MzQw
10
- NzM5MWVhNzU0ODEyMTZmZDU1Y2NjZDZiMTRlYmNmZTNjMDFkMmMwMGIzMWUz
11
- MjNmNTRkZDg4OTc5Mzg2ZGJjMTNiNzM0YjQyNWNhMjBhOTU3MDI=
9
+ MDA0ZjdjYWYwZTFiODRjZTdlNzk5YmJlOTc0ZGExZmQwOThlOWE0MWZjZWQ1
10
+ OTIzZjgxN2ZhY2M2OTM5YjVjMzUyY2I2NDE3ZWYwZDVjZTU0NmM1MTAyMWYx
11
+ NjBiNDFmMjhmMTljM2I5OTQ0ODRlNjA0MmIwZTA4MjljYmM5YjQ=
12
12
  data.tar.gz: !binary |-
13
- ZDRhYzExNDUyOTA1ODFkMDFkYmY5ODllZDcxZGIyOWE2N2M0ZTFlN2ZjYWYz
14
- NDAwNTY3Njk2YWUyMDA3MzhmMjMyMjQzMjBiZGM0ZmNiNGVhNjNmNjZkYzVh
15
- Njc0ZWRmOTQwYWFlNDhkYzMyZTk2ZThlOWY5NTEyMGYyMTc2YTk=
13
+ YTU5Y2NlMmIyZWUxODIzODI4ZWMzZTk2OTM3ZTViMmY1OGM4MTBkYjU5OGEy
14
+ MjkzMmQwZjBkMWM3Y2ZjOTM4MmJiNWZiMGI0YWY3MjE5ZmJkMDk2NzdmNDgy
15
+ MTkwYzBmYThlNGQ4MDZiMTcyYjRhYjBiYzdhNmE0ZmNhZDczYWE=
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  rvm:
2
- - "2.1.1"
2
+ - "2.1.2"
3
3
  - "2.0.0"
4
4
  - "1.9.3"
5
5
  - "1.8.7"
6
- - "jruby-1.7.11"
6
+ - "jruby-1.7.12"
data/CHANGES.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # `meta_events` Changelog
2
2
 
3
+ ### 1.2.0, 30 June 2014
4
+
5
+ * You can now customize the separator used in nested properties by passing (_e.g._) `:property_separator => ' '` to
6
+ the declaration of a `version` in the definition DSL. This allows you to get nested properties named things like
7
+ `user age`, `user name`, and so on, rather than `user_age` and `user_name`. (Thanks to
8
+ [Harm de Wit](https://github.com/harmdewit) for the idea.)
9
+ * If you passed in a `Time` object to an `#event!` call, `meta_events` was calling `#utc` on it to normalize it to
10
+ UTC...and `Time#utc` (unbeknownst to me) _modifies_ its receiver, which is really bad. Now we call `Time#getutc`
11
+ instead, which doesn't do that. (Big shout-out to [Pete Sharum](https://github.com/petesharum) for catching and
12
+ fixing this, along with a spec for the fix!)
13
+ * Added syntax highlighting to the README.
14
+ * Bumped versions of Ruby we test against under Travis to the latest ones.
15
+
3
16
  ### 1.1.2, 29 May 2014
4
17
 
5
18
  * The `:external_name` on an Event was not correctly passed (instead of the fully-qualified event name) when using
data/CONTRIBUTORS.md ADDED
@@ -0,0 +1,17 @@
1
+ # Contributors to `meta_events`
2
+
3
+ Created and maintained by [Andrew Geweke](https://github.com/ageweke) under the support of the wonderful folks at
4
+ [Swiftype, Inc.](https://swiftype.com/).
5
+
6
+ Additional contributions by:
7
+
8
+ * [Aaron Lerch](https://github.com/aaronlerch): Support for custom external event names, so that end users of
9
+ Mixpanel and other tools receiving data from `meta_events` can get nice, pretty, human-readable event names.
10
+ * [Pete Sharum](https://github.com/petesharum): Fix for `Time` objects passed in; turns out `Time#utc` _modifies_ its
11
+ receiver, which is bad.
12
+ * [Jesse Rusak](https://github.com/jder): doc typos and fixes for usage of `Rails.logger`.
13
+
14
+ Inspiration for ideas by:
15
+
16
+ * [Harm de Wit](https://github.com/harmdewit) for adding a configurable `properties_separator` on the `version` in the
17
+ events DSL.
data/README.md CHANGED
@@ -4,16 +4,15 @@ and a powerful properties system that makes it easy to pass large numbers of con
4
4
 
5
5
  MetaEvents supports:
6
6
 
7
- * Ruby 1.8.7, 1.9.3, 2.0.0, 2.1.1, or JRuby 1.7.11
7
+ * Ruby 1.8.7, 1.9.3, 2.0.0, 2.1.2, or JRuby 1.7.12
8
8
 
9
9
  These are, however, just the versions it's tested against; MetaEvents contains no code that should be at all
10
10
  particularly dependent on exact Ruby versions, and should be compatible with a broad set of versions.
11
11
 
12
12
  Current build status: ![Current Build Status](https://api.travis-ci.org/swiftype/meta_events.png?branch=master)
13
13
 
14
- Brought to you by the folks at [Swiftype](https://www.swiftype.com/). First version written by [Andrew Geweke](https://www.github.com/ageweke). Major contributions by:
15
-
16
- * [Aaron Lerch](https://github.com/aaronlerch): support for sending human-readable event names to Mixpanel via `:external_name` in the DSL on an Event, or via `:external_name` in `MetaEvents::Tracker#initialize` or `MetaEvents::Tracker.default_external_name`.
14
+ Brought to you by the folks at [Swiftype](https://www.swiftype.com/). First version written by
15
+ [Andrew Geweke](https://www.github.com/ageweke). For additional contributors, see [CONTRIBUTORS](CONTRIBUTORS.md).
17
16
 
18
17
  ### Background
19
18
 
@@ -61,13 +60,15 @@ First, let's declare an event that we want to fire. Create `config/meta_events.r
61
60
  configures this as your events file if you're using Rails; if not, use `MetaEvents::Tracker.default_definitions =` to
62
61
  set the path to whatever file you like):
63
62
 
64
- global_events_prefix :ab
63
+ ```ruby
64
+ global_events_prefix :ab
65
65
 
66
- version 1, "2014-02-04" do
67
- category :user do
68
- event :signed_up, "2014-02-04", "user creates a brand-new account"
69
- end
70
- end
66
+ version 1, "2014-02-04" do
67
+ category :user do
68
+ event :signed_up, "2014-02-04", "user creates a brand-new account"
69
+ end
70
+ end
71
+ ```
71
72
 
72
73
  Let's walk through this:
73
74
 
@@ -95,25 +96,29 @@ that's signed in, which is almost always just the primary key from the `users` t
95
96
  currently signed in. We also pass it the IP address of the user (which can safely be `nil`); Mixpanel, for example,
96
97
  uses this for doing geolocation of users:
97
98
 
98
- class ApplicationController < ActionController::Base
99
- ...
100
- def meta_events_tracker
101
- @meta_events_tracker ||= MetaEvents::Tracker.new(current_user.try(:id), request.remote_ip)
102
- end
103
- ...
104
- end
99
+ ```ruby
100
+ class ApplicationController < ActionController::Base
101
+ ...
102
+ def meta_events_tracker
103
+ @meta_events_tracker ||= MetaEvents::Tracker.new(current_user.try(:id), request.remote_ip)
104
+ end
105
+ ...
106
+ end
107
+ ```
105
108
 
106
109
  Now, from the controller, we can fire an event and pass a couple of properties:
107
110
 
108
- class UsersController < ApplicationController
109
- ...
110
- def create
111
- ...
112
- meta_events_tracker.event!(:user, :signed_up, { :user_gender => @new_user.gender, :user_age => @new_user.age })
113
- ...
114
- end
115
- ...
116
- end
111
+ ```ruby
112
+ class UsersController < ApplicationController
113
+ ...
114
+ def create
115
+ ...
116
+ meta_events_tracker.event!(:user, :signed_up, { :user_gender => @new_user.gender, :user_age => @new_user.age })
117
+ ...
118
+ end
119
+ ...
120
+ end
121
+ ```
117
122
 
118
123
  We're just about all done; but, right now, the event isn't actually going anywhere, because we haven't configured any
119
124
  _event receivers_.
@@ -129,7 +134,9 @@ transparently), and `Time` objects.
129
134
  Fortunately, the [Mixpanel](https://github.com/mixpanel/mixpanel-ruby) Gem complies with this interface perfectly.
130
135
  So, in `config/environments/production.rb` (or any other file that loads before your first event gets fired):
131
136
 
132
- MetaEvents::Tracker.default_event_receivers << Mixpanel::Tracker.new("0123456789abcdef")
137
+ ```ruby
138
+ MetaEvents::Tracker.default_event_receivers << Mixpanel::Tracker.new("0123456789abcdef")
139
+ ```
133
140
 
134
141
  (where `0123456789abcdef` is actually your Mixpanel API token)
135
142
 
@@ -137,7 +144,9 @@ In our development environment, we may or may not want to include Mixpanel itsel
137
144
  Mixpanel event receiver, above); however, we might also want to print events to the console or some other file as
138
145
  they are fired. So, in `config/environments/development.rb`:
139
146
 
140
- MetaEvents::Tracker.default_event_receivers << MetaEvents::TestReceiver.new
147
+ ```ruby
148
+ MetaEvents::Tracker.default_event_receivers << MetaEvents::TestReceiver.new
149
+ ```
141
150
 
142
151
  This will print events as they are fired to your Rails log (_e.g._, `log/development.log`); you can pass an argument
143
152
  to the constructor of `TestReceiver` that's a `Logger`, an `IO` (_e.g._, `STDOUT`, `STDERR`, an open `File` object),
@@ -186,13 +195,17 @@ picks up these attributes, decodes them, and calls any function you want with th
186
195
 
187
196
  As an example, in a view, you simply convert:
188
197
 
189
- <%= link_to("go here", user_awesome_path, :class => "my_class") %>
198
+ ```html+erb
199
+ <%= link_to("go here", user_awesome_path, :class => "my_class") %>
200
+ ```
190
201
 
191
202
  ...to:
192
203
 
193
- <%= meta_events_tracked_link_to("go here", user_awesome_path, :class => "my_class",
194
- :meta_event => { :category => :user, :event => :awesome,
195
- :properties => { :color => 'green' } }) %>
204
+ ```html+erb
205
+ <%= meta_events_tracked_link_to("go here", user_awesome_path, :class => "my_class",
206
+ :meta_event => { :category => :user, :event => :awesome,
207
+ :properties => { :color => 'green' } }) %>
208
+ ```
196
209
 
197
210
  (Not immediately obvious: the `:meta_event` attribute is just part of the `html_options` `Hash` that
198
211
  `link_to` accepts, not an additional parameter. `meta_events_tracked_link_to` accepts exactly the same parameters as
@@ -200,12 +213,16 @@ As an example, in a view, you simply convert:
200
213
 
201
214
  This automatically turns the generated HTML from:
202
215
 
203
- <a href="/users/awesome" class="my_class">go here</a>
216
+ ```html
217
+ <a href="/users/awesome" class="my_class">go here</a>
218
+ ```
204
219
 
205
220
  to something like this:
206
221
 
207
- <a href="/users/awesome" class="my_class mejtp_trk" data-mejtp-event="ab1_user_awesome"
208
- data-mejtp-prp="{&quot;ip&quot;:&quot;127.0.0.1&quot;,&quot;color&quot;:&quot;green&quot;,&quot;implicit_prop_1&quot;:&quot;someValue&quot;}">go here</a>
222
+ ```html
223
+ <a href="/users/awesome" class="my_class mejtp_trk" data-mejtp-event="ab1_user_awesome"
224
+ data-mejtp-prp="{&quot;ip&quot;:&quot;127.0.0.1&quot;,&quot;color&quot;:&quot;green&quot;,&quot;implicit_prop_1&quot;:&quot;someValue&quot;}">go here</a>
225
+ ```
209
226
 
210
227
  `mejtp` stands for "MetaEvents JavaScript Tracking Prefix", and is simply a likely-unique prefix for these values.
211
228
  (You can change it with `MetaEvents::Helpers.meta_events_javascript_tracking_prefix 'foo'`.) `mejtp_trk` is the class
@@ -214,15 +231,19 @@ of the event, and a JSON-encoded string of all the properties (both implicit and
214
231
 
215
232
  Now, add this to a Javascript file in your application:
216
233
 
217
- //= require meta_events
234
+ ```javascript
235
+ //= require meta_events
236
+ ```
218
237
 
219
238
  And, finally, call something like this:
220
239
 
221
- $(document).ready(function() {
222
- MetaEvents.forAllTrackableElements(document, function(id, element, eventName, properties) {
223
- mixpanel.track_links("#" + id, eventName, properties);
224
- })
225
- });
240
+ ```javascript
241
+ $(document).ready(function() {
242
+ MetaEvents.forAllTrackableElements(document, function(id, element, eventName, properties) {
243
+ mixpanel.track_links("#" + id, eventName, properties);
244
+ })
245
+ });
246
+ ```
226
247
 
227
248
  `MetaEvents.forAllTrackableElements` accepts a root element to start searching at, and a callback function. It finds
228
249
  all elements with class `mejtp_trk` on them underneath that element, extracts the event name and properties, and adds
@@ -248,48 +269,56 @@ extra work &mdash; otherwise, how would we get those properties? However, it's n
248
269
  First off, make sure you get this into your layout in a `<script>` tag somewhere &mdash; at the bottom of the page is
249
270
  perfectly fine:
250
271
 
251
- <%= meta_events_frontend_events_javascript %>
272
+ ```html+erb
273
+ <%= meta_events_frontend_events_javascript %>
274
+ ```
252
275
 
253
276
  This allows MetaEvents to pass event data properly from the backend to the frontend for any events you'll be firing.
254
277
 
255
278
  Now, as an example, let's imagine we implement a JavaScript game on our site, and want to fire events when the user
256
279
  wins, loses, or gets a new high score. First, let's define those in our DSL:
257
280
 
258
- global_events_prefix :ab
281
+ ```ruby
282
+ global_events_prefix :ab
259
283
 
260
- version 1, "2014-02-11" do
261
- category :jsgame do
262
- event :won, "2014-02-11", "user won a game!"
263
- event :lost, "2014-02-11", "user lost a game"
264
- event :new_high_score, "2014-02-11", "user got a new high score"
265
- end
266
- end
284
+ version 1, "2014-02-11" do
285
+ category :jsgame do
286
+ event :won, "2014-02-11", "user won a game!"
287
+ event :lost, "2014-02-11", "user lost a game"
288
+ event :new_high_score, "2014-02-11", "user got a new high score"
289
+ end
290
+ end
291
+ ```
267
292
 
268
293
  Now, in whatever controller action renders the page that the game is on, we need to _register_ these events. This
269
294
  tells the front-end integration that we might fire them from the resulting page; it therefore embeds JavaScript in the
270
295
  page that defines the set of properties for those events, so that the front end has access to the data it needs:
271
296
 
272
- class GameController < ApplicationController
273
- def game
274
- ...
275
- meta_events_define_frontend_event(:jsgame, :won, { :winning_streak => current_winning_streak })
276
- meta_events_define_frontend_event(:jsgame, :lost, { :losing_streak => current_losing_streak })
277
- meta_events_define_frontend_event(:jsgame, :new_high_score, { :previous_high_score => current_high_score })
278
- ...
279
- end
280
- end
297
+ ```ruby
298
+ class GameController < ApplicationController
299
+ def game
300
+ ...
301
+ meta_events_define_frontend_event(:jsgame, :won, { :winning_streak => current_winning_streak })
302
+ meta_events_define_frontend_event(:jsgame, :lost, { :losing_streak => current_losing_streak })
303
+ meta_events_define_frontend_event(:jsgame, :new_high_score, { :previous_high_score => current_high_score })
304
+ ...
305
+ end
306
+ end
307
+ ```
281
308
 
282
309
  This will allow us to make the following calls in the frontend, from our game code:
283
310
 
284
- if (wonGame) {
285
- MetaEvents.event('jsgame_won');
286
- } else {
287
- MetaEvents.event('jsgame_lost');
288
- }
311
+ ```javascript
312
+ if (wonGame) {
313
+ MetaEvents.event('jsgame_won');
314
+ } else {
315
+ MetaEvents.event('jsgame_lost');
316
+ }
289
317
 
290
- if (currentScore > highScore) {
291
- MetaEvents.event('jsgame_new_high_score', { score: currentScore });
292
- }
318
+ if (currentScore > highScore) {
319
+ MetaEvents.event('jsgame_new_high_score', { score: currentScore });
320
+ }
321
+ ```
293
322
 
294
323
  What's happened here is that `meta_events_define_frontend_event` took the set of properties you passed, merged them
295
324
  with any implicit properties defined, and passed them to the frontend via the `meta_events_frontend_events_javascript`
@@ -304,21 +333,24 @@ event alias; they will all be merged together, along with the properties supplie
304
333
  If you need to be able to fire the exact same event with _different_ sets of properties from different places in a
305
334
  single page, you can alias the event using the `:name` property:
306
335
 
307
- class GameController < ApplicationController
308
- def game
309
- ...
310
- meta_events_define_frontend_event(:jsgame, :paused_game, { :while => :winning }, { :name => :paused_while_winning })
311
- meta_events_define_frontend_event(:jsgame, :paused_game, { :while => :losing }, { :name => :paused_while_losing })
312
- ...
313
- end
314
- end
315
-
336
+ ```ruby
337
+ class GameController < ApplicationController
338
+ def game
316
339
  ...
317
- if (winning) {
318
- MetaEvents.event('paused_while_winning');
319
- } else {
320
- MetaEvents.event('paused_while_losing');
321
- }
340
+ meta_events_define_frontend_event(:jsgame, :paused_game, { :while => :winning }, { :name => :paused_while_winning })
341
+ meta_events_define_frontend_event(:jsgame, :paused_game, { :while => :losing }, { :name => :paused_while_losing })
342
+ ...
343
+ end
344
+ end
345
+ ```
346
+ ```javascript
347
+ ...
348
+ if (winning) {
349
+ MetaEvents.event('paused_while_winning');
350
+ } else {
351
+ MetaEvents.event('paused_while_losing');
352
+ }
353
+ ```
322
354
 
323
355
  Both calls from the JavaScript will fire the event `ab1_jsgame_paused_game`, but one of them will pass
324
356
  `while: 'winning'`, and the other `while: 'losing'`.
@@ -382,21 +414,23 @@ going to want to pass properties about that user to any event that happens in th
382
414
  You could add these to every single call to `#event!`, but MetaEvents has a better way. When you create the
383
415
  `MetaEvents::Tracker` instance, you can define _implicit properties_. Let's add some now:
384
416
 
385
- class ApplicationController < ActionController::Base
386
- ...
387
- def meta_events_tracker
388
- implicit_properties = { }
389
- if current_user
390
- implicit_properties.merge!(
391
- :user_gender => current_user.gender,
392
- :user_age => current_user.age
393
- )
394
- end
395
- @meta_events_tracker ||= MetaEvents::Tracker.new(current_user.try(:id), request.remote_ip,
396
- :implicit_properties => implicit_properties)
397
- end
398
- ...
417
+ ```ruby
418
+ class ApplicationController < ActionController::Base
419
+ ...
420
+ def meta_events_tracker
421
+ implicit_properties = { }
422
+ if current_user
423
+ implicit_properties.merge!(
424
+ :user_gender => current_user.gender,
425
+ :user_age => current_user.age
426
+ )
399
427
  end
428
+ @meta_events_tracker ||= MetaEvents::Tracker.new(current_user.try(:id), request.remote_ip,
429
+ :implicit_properties => implicit_properties)
430
+ end
431
+ ...
432
+ end
433
+ ```
400
434
 
401
435
  Now, these properties will get passed on every event fired by this Tracker. (This is, in fact, the biggest
402
436
  consideration when deciding when and where you'll create new `MetaEvents::Tracker` instances: implicit properties are
@@ -410,31 +444,33 @@ properties that are defined on it. For example, imagine we have an event trigger
410
444
  another user. We have at least three entities: the 'from' user, the 'to' user, and the message itself. If we really
411
445
  want to instrument this event properly, we're going to want something like this:
412
446
 
413
- meta_events_tracker.event!(:user, :sent_message, {
414
- :from_user_country => from_user.country,
415
- :from_user_state => from_user.state,
416
- :from_user_postcode => from_user.postcode,
417
- :from_user_city => from_user.city,
418
- :from_user_language => from_user.language,
419
- :from_user_referred_from => from_user.referred_from,
420
- :from_user_gender => from_user.gender,
421
- :from_user_age => from_user.age,
422
-
423
- :to_user_country => to_user.country,
424
- :to_user_state => to_user.state,
425
- :to_user_postcode => to_user.postcode,
426
- :to_user_city => to_user.city,
427
- :to_user_language => to_user.language,
428
- :to_user_referred_from => to_user.referred_from,
429
- :to_user_gender => to_user.gender,
430
- :to_user_age => to_user.age,
431
-
432
- :message_sent_at => message.sent_at,
433
- :message_type => message.type,
434
- :message_length => message.length,
435
- :message_language => message.language,
436
- :message_attachments => message.attachments?
437
- })
447
+ ```ruby
448
+ meta_events_tracker.event!(:user, :sent_message, {
449
+ :from_user_country => from_user.country,
450
+ :from_user_state => from_user.state,
451
+ :from_user_postcode => from_user.postcode,
452
+ :from_user_city => from_user.city,
453
+ :from_user_language => from_user.language,
454
+ :from_user_referred_from => from_user.referred_from,
455
+ :from_user_gender => from_user.gender,
456
+ :from_user_age => from_user.age,
457
+
458
+ :to_user_country => to_user.country,
459
+ :to_user_state => to_user.state,
460
+ :to_user_postcode => to_user.postcode,
461
+ :to_user_city => to_user.city,
462
+ :to_user_language => to_user.language,
463
+ :to_user_referred_from => to_user.referred_from,
464
+ :to_user_gender => to_user.gender,
465
+ :to_user_age => to_user.age,
466
+
467
+ :message_sent_at => message.sent_at,
468
+ :message_type => message.type,
469
+ :message_length => message.length,
470
+ :message_language => message.language,
471
+ :message_attachments => message.attachments?
472
+ })
473
+ ```
438
474
 
439
475
  Needless to say, this kind of sucks. Either we're going to end up with a ton of duplicate, unmaintainable code, or
440
476
  we'll just cut back and only pass a few properties &mdash; greatly reducing the possibilities of our analytics
@@ -445,40 +481,44 @@ system.
445
481
  We can improve this situation by using a feature of MetaEvents: when properties are nested in sub-hashes, they get
446
482
  automatically expanded and their names prefixed by the outer hash key. So let's define a couple of methods on models:
447
483
 
448
- class User < ActiveRecord::Base
449
- def to_event_properties
450
- {
451
- :country => country,
452
- :state => state,
453
- :postcode => postcode,
454
- :city => city,
455
- :language => language,
456
- :referred_from => referred_from,
457
- :gender => gender,
458
- :age => age
459
- }
460
- end
461
- end
462
-
463
- class Message < ActiveRecord::Base
464
- def to_event_properties
465
- {
466
- :sent_at => sent_at,
467
- :type => type,
468
- :length => length,
469
- :language => language,
470
- :attachments => attachments?
471
- }
472
- end
473
- end
484
+ ```ruby
485
+ class User < ActiveRecord::Base
486
+ def to_event_properties
487
+ {
488
+ :country => country,
489
+ :state => state,
490
+ :postcode => postcode,
491
+ :city => city,
492
+ :language => language,
493
+ :referred_from => referred_from,
494
+ :gender => gender,
495
+ :age => age
496
+ }
497
+ end
498
+ end
499
+
500
+ class Message < ActiveRecord::Base
501
+ def to_event_properties
502
+ {
503
+ :sent_at => sent_at,
504
+ :type => type,
505
+ :length => length,
506
+ :language => language,
507
+ :attachments => attachments?
508
+ }
509
+ end
510
+ end
511
+ ```
474
512
 
475
513
  Now, we can pass the exact same set of properties as the above example, by simply doing:
476
514
 
477
- meta_events_tracker.event!(:user, :sent_message, {
478
- :from_user => from_user.to_event_properties,
479
- :to_user => to_user.to_event_properties,
480
- :message => message.to_event_properties
481
- })
515
+ ```ruby
516
+ meta_events_tracker.event!(:user, :sent_message, {
517
+ :from_user => from_user.to_event_properties,
518
+ :to_user => to_user.to_event_properties,
519
+ :message => message.to_event_properties
520
+ })
521
+ ```
482
522
 
483
523
  **SO** much better.
484
524
 
@@ -488,7 +528,9 @@ And &mdash; tah-dah! &mdash; MetaEvents supports this syntax automatically. If y
488
528
  that object defines a method called `#to_event_properties`, then it will be called automatically, and replaced.
489
529
  Our code now looks like:
490
530
 
491
- meta_events_tracker.event!(:user, :sent_message, { :from_user => from_user, :to_user => to_user, :message => message })
531
+ ```ruby
532
+ meta_events_tracker.event!(:user, :sent_message, { :from_user => from_user, :to_user => to_user, :message => message })
533
+ ```
492
534
 
493
535
  ### How to Take the Most Advantage
494
536
 
@@ -515,14 +557,16 @@ Because there is such a wide variety of these systems available, MetaEvents does
515
557
  them &mdash; doing so would be a great deal of effort, and yet still unlikely to satisfy most users. Instead,
516
558
  MetaEvents makes it very easy to use any package you want:
517
559
 
518
- class MyEventReceiver
519
- def track(distinct_id, event_name, event_properties)
520
- # Call Resque, Sidekiq, or anything else you want here; enqueue a job that, when run, will call:
521
- # Mixpanel::Tracker.new($my_mixpanel_api_key).track(distinct_id, event_name, event_properties)
522
- end
523
- end
560
+ ```ruby
561
+ class MyEventReceiver
562
+ def track(distinct_id, event_name, event_properties)
563
+ # Call Resque, Sidekiq, or anything else you want here; enqueue a job that, when run, will call:
564
+ # Mixpanel::Tracker.new($my_mixpanel_api_key).track(distinct_id, event_name, event_properties)
565
+ end
566
+ end
524
567
 
525
- MetaEvents::Tracker.default_event_receivers << MyEventReceiver.new
568
+ MetaEvents::Tracker.default_event_receivers << MyEventReceiver.new
569
+ ```
526
570
 
527
571
  Voilà &mdash; asynchronous event tracking.
528
572
 
@@ -551,14 +595,16 @@ control, but that's a pain.)
551
595
 
552
596
  Rather than doing this, you can retire them:
553
597
 
554
- global_events_prefix :ab
598
+ ```ruby
599
+ global_events_prefix :ab
555
600
 
556
- version 1, "2014-02-04" do
557
- category :user do
558
- event :logged_in_with_facebook, "2014-02-04", "user creates a brand-new account", :retired_at => "2014-06-01"
559
- event :signed_up, "2014-02-04", "user creates a brand-new account"
560
- end
561
- end
601
+ version 1, "2014-02-04" do
602
+ category :user do
603
+ event :logged_in_with_facebook, "2014-02-04", "user creates a brand-new account", :retired_at => "2014-06-01"
604
+ event :signed_up, "2014-02-04", "user creates a brand-new account"
605
+ end
606
+ end
607
+ ```
562
608
 
563
609
  Given the above, trying to call `event!(:user, :logged_in_with_facebook)` will fail with an exception, because the
564
610
  event has been retired. (Note that, once again, the actual date passed to `:retired_at` is simply for record-keeping
@@ -572,15 +618,17 @@ of what things were in the past, as well as what they are today.
572
618
  You can also add notes to events. They must be tagged with the author and the time, and they can be very useful for
573
619
  documenting changes:
574
620
 
575
- global_events_prefix :ab
621
+ ```ruby
622
+ global_events_prefix :ab
576
623
 
577
- version 1, "2014-02-04" do
578
- category :user do
579
- event :signed_up, "2014-02-04", "user creates a brand-new account" do
580
- note "2014-03-17", "jsmith", "Moved sign-up button to the home page -- should increase signups significantly"
581
- end
582
- end
624
+ version 1, "2014-02-04" do
625
+ category :user do
626
+ event :signed_up, "2014-02-04", "user creates a brand-new account" do
627
+ note "2014-03-17", "jsmith", "Moved sign-up button to the home page -- should increase signups significantly"
583
628
  end
629
+ end
630
+ end
631
+ ```
584
632
 
585
633
  This allows you to record changes to events, as well as the events themselves.
586
634
 
@@ -635,28 +683,63 @@ override these external names.
635
683
 
636
684
  First, you can override them globally for all `MetaEvents::Tracker` instances:
637
685
 
638
- MetaEvents::Tracker.default_external_name = lambda { |event| "#{event.category_name} #{event.name}" }
686
+ ```ruby
687
+ MetaEvents::Tracker.default_external_name = lambda { |event| "#{event.category_name} #{event.name}" }
688
+ ```
639
689
 
640
690
  Second, you can override them for a specific `MetaEvents::Tracker` instance:
641
691
 
642
- MetaEvents::Tracker.new(current_user.try(:id),
643
- request.remote_ip,
644
- :external_name => lambda { |event| "#{event.category_name} #{event.name}" }
645
- )
692
+ ```ruby
693
+ MetaEvents::Tracker.new(current_user.try(:id),
694
+ request.remote_ip,
695
+ :external_name => lambda { |event| "#{event.category_name} #{event.name}" }
696
+ )
697
+ ```
646
698
 
647
699
  Finally, you can override each event's external name in the events DSL:
648
700
 
649
- global_events_prefix :ab
701
+ ```ruby
702
+ global_events_prefix :ab
650
703
 
651
- version 1, "2014-02-11" do
652
- category :example_category do
653
- event :example_event, "2014-02-11", "Example was exampled!", :external_name => 'ex. was ex.'
654
- end
655
- end
704
+ version 1, "2014-02-11" do
705
+ category :example_category do
706
+ event :example_event, "2014-02-11", "Example was exampled!", :external_name => 'ex. was ex.'
707
+ end
708
+ end
709
+ ```
656
710
 
657
711
  The order of precedence for determining the external event name is the DSL's `event :external_name => 'foo'`,
658
712
  `MetaEvents::Tracker.new`, `MetaEvents::Tracker.default_external_name`, built-in default.
659
713
 
714
+ ### Customizing the Nested Property Separator
715
+
716
+ Similarly, while developers might be perfectly comfortable with (and even prefer) expanded properties named things
717
+ like `user_age`, `user_name`, and so on, others might want a different separator (like a space character). When
718
+ defining a version, you can set this, as follows:
719
+
720
+ ```ruby
721
+ global_events_prefix :ab
722
+
723
+ version 1, "2014-02-11", :property_separator => ' ' do
724
+ category :example_category do
725
+ event :example_event, "2014-02-11", "Example was exampled!"
726
+ end
727
+ end
728
+ ```
729
+
730
+ Now, assuming `@user` is a `User` object that responds to `#to_event_properties` (or is just a `Hash`):
731
+
732
+ ```ruby
733
+ tracker.event!(:example_category, :example_event, :user => @user)
734
+ ```
735
+
736
+ ...you'll get properties named `user age`, `user name`, and so on, rather than `user_age` and `user_name`.
737
+
738
+ Note that this is defined on the `version`, not the `category`, `event`, or even `#event!` call, because changing
739
+ this is _a big deal_ &mdash; changing property names almost always breaks all kinds of analysis you might want to do
740
+ with your analytics tool. However, the idea is that changing `version`s is a breaking change to your analytics
741
+ system anyway, so you can certainly set it on a new `version` to something different.
742
+
660
743
  ## Contributing
661
744
 
662
745
  1. Fork it ( http://github.com/swiftype/meta_events/fork )
@@ -15,6 +15,8 @@ module MetaEvents
15
15
  class Version
16
16
  attr_reader :definition_set, :number, :introduced
17
17
 
18
+ DEFAULT_PROPERTY_SEPARATOR = "_"
19
+
18
20
  # Creates a new instance. +definition_set+ is the MetaEvents::Definition::DefinitionSet to which this Version belongs;
19
21
  # +number+ is an integer telling you, well, which version this is -- it must be unique within the DefinitionSet.
20
22
  # +introduced+ is a String that must be parseable using Time.parse; this should be the date (and time, if you
@@ -43,9 +45,10 @@ module MetaEvents
43
45
  @introduced = Time.parse(introduced)
44
46
  @categories = { }
45
47
 
46
- options.assert_valid_keys(:retired_at)
48
+ options.assert_valid_keys(:retired_at, :property_separator)
47
49
 
48
50
  @retired_at = Time.parse(options[:retired_at]) if options[:retired_at]
51
+ @property_separator = options[:property_separator] || DEFAULT_PROPERTY_SEPARATOR
49
52
 
50
53
  instance_eval(&block) if block
51
54
  end
@@ -80,6 +83,23 @@ module MetaEvents
80
83
  @retired_at
81
84
  end
82
85
 
86
+ # Returns the string that should be used in property expansion to separate the two (or more) parts of the
87
+ # property name. For example, if you pass to +MetaEvents::Tracker#event!+ in +additional_properties+
88
+ # (or in the implicit properties taken by +MetaEvents::Tracker#initialize+) data that looks like
89
+ # <tt>:user => { :first_name => 'Fiona', :last_name => 'Darling' }</tt>, then, by default, you end up
90
+ # with properties named +user_first_name+ and +user_last_name+.
91
+ #
92
+ # If, instead, you set <tt>:property_separator => '~'</tt> on your Version, then you end up with properties
93
+ # named +user~first_name+ and +user~last_name+. This can be used to change property names to your liking.
94
+ #
95
+ # This is defined on the +Version+ so that you can change it if you redo your entire events system. It is not
96
+ # defined at a lower level, because having property names change is a _big deal_ -- it breaks data analysis
97
+ # in an analytics system, and so you generally should not change it unless you're changing lots of other stuff
98
+ # that would break analytics as well, like when defining an entire new Version.
99
+ def property_separator
100
+ @property_separator
101
+ end
102
+
83
103
  # Override #to_s, for a cleaner view.
84
104
  def to_s
85
105
  "<Version #{number}>"
@@ -224,10 +224,10 @@ module MetaEvents
224
224
  #
225
225
  # There might be a situation where users performing analysis desire a friendlier name than the default.
226
226
  # The external name can be customized with a lambda (or any object that responds to <tt>#call(event)</tt>).
227
- # To customize the external name for all MetaEvents::Tracker instances,
227
+ # To customize the external name for all MetaEvents::Tracker instances,
228
228
  # specify <tt>MetaEvents::Tracker.default_external_name = lambda { |event| "custom event name" }</tt>.
229
- #
230
- # To customize the external name for a specific MetaEvents::Tracker instance, pass the lambda
229
+ #
230
+ # To customize the external name for a specific MetaEvents::Tracker instance, pass the lambda
231
231
  # in the constructor, for example:
232
232
  # <tt>MetaEvents::Tracker.new(current_user.id, request.remote_ip, :external_name => lambda {|e| "#{e.full_name}_CUSTOM" })</tt>
233
233
  #
@@ -333,7 +333,7 @@ module MetaEvents
333
333
  # with every event fired from this Tracker. This can use the hash-merge and object syntax
334
334
  # (#to_event_properties) documented above. Any properties explicitly passed with an event
335
335
  # that have the same name as these properties will override these properties for that event.
336
- # [:external_name] If present, this should be a lambda that takes a single argument and returns a string, or an
336
+ # [:external_name] If present, this should be a lambda that takes a single argument and returns a string, or an
337
337
  # object that responds to call(event). If +:external_name+ is not provided, it will use the
338
338
  # default configured for the MetaEvents::Tracker class.
339
339
  def initialize(distinct_id, ip, options = { })
@@ -351,11 +351,11 @@ module MetaEvents
351
351
  @definitions = ::MetaEvents::Definition::DefinitionSet.from(definitions)
352
352
  @version = options[:version] || self.class.default_version || raise(ArgumentError, "Must specify a :version")
353
353
  @external_name = options[:external_name] || self.class.default_external_name || raise(ArgumentError, "Must specify an :external_name")
354
- raise ArgumentError, ":external_name option must respond to #call" unless @external_name.respond_to? :call
354
+ raise ArgumentError, ":external_name option must respond to #call" unless @external_name.respond_to?(:call)
355
355
 
356
356
  @implicit_properties = { }
357
- self.class.merge_properties(@implicit_properties, { :ip => normalize_ip(ip).to_s }) if ip
358
- self.class.merge_properties(@implicit_properties, options[:implicit_properties] || { })
357
+ self.class.merge_properties(@implicit_properties, { :ip => normalize_ip(ip).to_s }, property_separator) if ip
358
+ self.class.merge_properties(@implicit_properties, options[:implicit_properties] || { }, property_separator)
359
359
  self.distinct_id = distinct_id if distinct_id
360
360
 
361
361
  self.event_receivers = Array(options[:event_receivers] || self.class.default_event_receivers.dup)
@@ -411,7 +411,7 @@ module MetaEvents
411
411
  event = version_object.fetch_event(category_name, event_name)
412
412
 
413
413
  explicit = { }
414
- self.class.merge_properties(explicit, additional_properties)
414
+ self.class.merge_properties(explicit, additional_properties, property_separator)
415
415
  properties = @implicit_properties.merge(explicit)
416
416
 
417
417
  event.validate!(properties)
@@ -440,6 +440,11 @@ module MetaEvents
440
440
  @definitions.fetch_version(version)
441
441
  end
442
442
 
443
+ # Returns the separator we should use when creating property names for nested properties.
444
+ def property_separator
445
+ version_object.property_separator
446
+ end
447
+
443
448
  # Accepts an IP address (or nil) in String, Integer, or IPAddr formats, and returns an IPAddr (or nil).
444
449
  def normalize_ip(ip)
445
450
  case ip
@@ -472,7 +477,7 @@ module MetaEvents
472
477
  #
473
478
  # +depth+ should be an integer, indicating how many layers of recursive calls we've invoked; this is simply to
474
479
  # prevent infinite recursion -- if this exceeds +MAX_DEPTH+, above, then an exception will be raised.
475
- def merge_properties(target, source, prefix = nil, depth = 0)
480
+ def merge_properties(target, source, separator, prefix = nil, depth = 0)
476
481
  if depth > MAX_DEPTH
477
482
  raise "Nesting in EventTracker is too great; do you have a circular reference? " +
478
483
  "We reached depth: #{depth.inspect}; expanding: #{source.inspect} with prefix #{prefix.inspect} into #{target.inspect}"
@@ -497,10 +502,11 @@ module MetaEvents
497
502
 
498
503
  net_value = normalize_scalar_property_value(value)
499
504
  if net_value == :invalid_property_value
505
+ with_separator = "#{prefixed_key}#{separator}"
500
506
  if value.kind_of?(Hash)
501
- merge_properties(target, value, "#{prefixed_key}_", depth + 1)
507
+ merge_properties(target, value, separator, with_separator, depth + 1)
502
508
  elsif value.respond_to?(:to_event_properties)
503
- merge_properties(target, value.to_event_properties, "#{prefixed_key}_", depth + 1)
509
+ merge_properties(target, value.to_event_properties, separator, with_separator, depth + 1)
504
510
  else
505
511
  raise ArgumentError, "Event property #{prefixed_key.inspect} is not a valid scalar, Hash, or object that " +
506
512
  "responds to #to_event_properties, but rather #{value.inspect} (#{value.class.name})."
@@ -527,7 +533,7 @@ module MetaEvents
527
533
  when Numeric then value
528
534
  when String then value.strip
529
535
  when Symbol then value.to_s.strip
530
- when Time then value.utc.strftime("%Y-%m-%dT%H:%M:%S")
536
+ when Time then value.getutc.strftime("%Y-%m-%dT%H:%M:%S")
531
537
  when Array then
532
538
  out = value.map { |e| normalize_scalar_property_value(e) }
533
539
  out = :invalid_property_value if out.detect { |e| e == :invalid_property_value }
@@ -1,3 +1,3 @@
1
1
  module MetaEvents
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'meta_events/definition/version'
2
+
1
3
  describe ::MetaEvents::Definition::Version do
2
4
  let(:definition_set) do
3
5
  out = double("definition_set")
@@ -38,6 +40,15 @@ describe ::MetaEvents::Definition::Version do
38
40
  instance.prefix.should == "gep3_"
39
41
  end
40
42
 
43
+ it "should set the property_separator to underscore by default" do
44
+ instance.property_separator.should == "_"
45
+ end
46
+
47
+ it "should allow setting the property separator to something else in the constructor" do
48
+ i2 = klass.new(definition_set, 3, "2014-02-03", :property_separator => 'Z')
49
+ i2.property_separator.should == "Z"
50
+ end
51
+
41
52
  context "with one category" do
42
53
  let(:category) do
43
54
  out = double("category")
@@ -1,3 +1,4 @@
1
+ require "meta_events"
1
2
  require "active_support"
2
3
  require 'active_support/core_ext/numeric/time'
3
4
  require 'ipaddr'
@@ -19,6 +20,12 @@ describe MetaEvents::Tracker do
19
20
  event :quux, '2014-01-31', 'this is quux'
20
21
  end
21
22
  end
23
+
24
+ version 3, '2014-07-01', :property_separator => '~' do
25
+ category :bar do
26
+ event :baz, '2014-07-01', 'this is baz'
27
+ end
28
+ end
22
29
  end
23
30
  end
24
31
 
@@ -462,6 +469,18 @@ EOS
462
469
  expect_event('xy1_foo_bar', { 'user_name' => 'wilfred', 'user_hometown' => 'Fridley', 'location_city' => 'Edina', 'location_zip' => 55343 })
463
470
  end
464
471
 
472
+ it "should expand out nested properties using the #property_separator set on the version" do
473
+ i2 = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 3, :implicit_properties => { :user => @user } )
474
+ i2.event!(:bar, :baz, { :location => { :city => 'Edina', :zip => 55343 } })
475
+ expect_event('xy3_bar_baz', { 'user~name' => 'wilfred', 'user~hometown' => 'Fridley', 'location~city' => 'Edina', 'location~zip' => 55343 })
476
+ end
477
+
478
+ it "should expand out nested properties from #to_event_properties using the #property_separator set on the version" do
479
+ i2 = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 3, :implicit_properties => { :user => @user } )
480
+ i2.event!(:bar, :baz, { :location => tep_object(:city => 'Edina', :zip => 55343 ) })
481
+ expect_event('xy3_bar_baz', { 'user~name' => 'wilfred', 'user~hometown' => 'Fridley', 'location~city' => 'Edina', 'location~zip' => 55343 })
482
+ end
483
+
465
484
  it "should not allow firing a retired event" do
466
485
  expect { @instance.event!(:foo, :nolonger, { }) }.to raise_error(::MetaEvents::Definition::DefinitionSet::RetiredEventError)
467
486
  end
@@ -494,6 +513,13 @@ EOS
494
513
  expect(props).to eq('user_name' => 'bongo', 'user_hometown' => 'Fridley', 'baz_a' => 1, 'baz_b' => 'hoo')
495
514
  end
496
515
 
516
+ it "should respect the property_separator" do
517
+ i2 = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 3, :implicit_properties => { :user => @user } )
518
+ expect(i2.effective_properties(:bar, :baz)[:properties]).to eq('user~name' => 'wilfred', 'user~hometown' => 'Fridley')
519
+ props = i2.effective_properties(:bar, :baz, :baz => { :a => 1, :b => 'hoo' }, :'user~name' => 'bongo')[:properties]
520
+ expect(props).to eq('user~name' => 'bongo', 'user~hometown' => 'Fridley', 'baz~a' => 1, 'baz~b' => 'hoo')
521
+ end
522
+
497
523
  it "should include no additional keys" do
498
524
  expect(@instance.effective_properties(:foo, :bar).keys.sort_by(&:to_s)).to eq(%w{distinct_id event_name external_name properties}.map(&:to_sym).sort_by(&:to_s))
499
525
  end
@@ -502,7 +528,7 @@ EOS
502
528
  describe "#merge_properties" do
503
529
  def expand(hash)
504
530
  out = { }
505
- klass.merge_properties(out, hash, "", 0)
531
+ klass.merge_properties(out, hash, "Z", "", 0)
506
532
  out
507
533
  end
508
534
 
@@ -540,20 +566,20 @@ EOS
540
566
  end
541
567
 
542
568
  it "should recursively expand hashes" do
543
- expect(expand({ :foo => { :bar => ' whatEVs '} })).to eq({ 'foo_bar' => 'whatEVs' })
569
+ expect(expand({ :foo => { :bar => ' whatEVs '} })).to eq({ 'fooZbar' => 'whatEVs' })
544
570
  expect { expand({ :foo => { :bar => [ 1, 2, /foo/ ] } }) }.to raise_error(ArgumentError)
545
571
  end
546
572
 
547
573
  it "should call #to_event_properties for any object, and recursively expand that" do
548
- expect(expand(:baz => tep_object({ :foo => ' BaR '}))).to eq({ 'baz_foo' => 'BaR' })
549
- expect(expand(:baz => tep_object({ :foo => { :bar => ' yo yo yo '} }))).to eq({ 'baz_foo_bar' => 'yo yo yo' })
574
+ expect(expand(:baz => tep_object({ :foo => ' BaR '}))).to eq({ 'bazZfoo' => 'BaR' })
575
+ expect(expand(:baz => tep_object({ :foo => { :bar => ' yo yo yo '} }))).to eq({ 'bazZfooZbar' => 'yo yo yo' })
550
576
 
551
577
  subsidiary = tep_object({ :bar => :baz })
552
- expect(expand(:baz => tep_object({ :foo => subsidiary }))).to eq({ 'baz_foo_bar' => 'baz' })
578
+ expect(expand(:baz => tep_object({ :foo => subsidiary }))).to eq({ 'bazZfooZbar' => 'baz' })
553
579
  end
554
580
 
555
581
  it "should raise if it detects a property-name conflict" do
556
- expect { expand(:foo_bar => :quux1, :foo => { :bar => :quux }) }.to raise_error(MetaEvents::Tracker::PropertyCollisionError)
582
+ expect { expand(:fooZbar => :quux1, :foo => { :bar => :quux }) }.to raise_error(MetaEvents::Tracker::PropertyCollisionError)
557
583
  end
558
584
  end
559
585
 
@@ -584,5 +610,37 @@ EOS
584
610
  expect(klass.normalize_scalar_property_value(input)).to eq(output)
585
611
  end
586
612
  end
613
+
614
+ it "should not modify passed values" do
615
+ t = Time.parse("2008-09-04 3:46:12 PM -08:00")
616
+ [
617
+ nil,
618
+ true,
619
+ false,
620
+ 3,
621
+ 42.5e+17,
622
+ 3.months,
623
+ :foobar,
624
+ :' FooBar ',
625
+ ' FooBar ',
626
+ t,
627
+ [ "foo", :bar, 123, -9.45e+17, t, false, nil, true, " BoNk " ],
628
+ /foobar/,
629
+ Object.new
630
+ ].each do |input|
631
+ expect { klass.normalize_scalar_property_value(input) }.to_not change { input }
632
+ end
633
+
634
+ nan = (0.0 / 0.0)
635
+ expect { klass.normalize_scalar_property_value(nan) }.to_not change { nan.nan? }
636
+
637
+ infinity = (1.0 / 0.0)
638
+ expect { klass.normalize_scalar_property_value(infinity) }.to_not change { infinity.infinite? }
639
+
640
+ neg_infinity = -(1.0 / 0.0)
641
+ expect { klass.normalize_scalar_property_value(neg_infinity) }.to_not change { neg_infinity.infinite? }
642
+
643
+ expect { klass.normalize_scalar_property_value(t) }.to_not change { t.zone }
644
+ end
587
645
  end
588
646
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meta_events
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Geweke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-29 00:00:00.000000000 Z
11
+ date: 2014-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -96,6 +96,7 @@ files:
96
96
  - .gitignore
97
97
  - .travis.yml
98
98
  - CHANGES.md
99
+ - CONTRIBUTORS.md
99
100
  - Gemfile
100
101
  - LICENSE.txt
101
102
  - README.md
@@ -140,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
141
  version: '0'
141
142
  requirements: []
142
143
  rubyforge_project:
143
- rubygems_version: 2.2.1
144
+ rubygems_version: 2.2.2
144
145
  signing_key:
145
146
  specification_version: 4
146
147
  summary: Structured, documented, powerful event emitting library for Mixpanel and