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 +8 -8
- data/.travis.yml +2 -2
- data/CHANGES.md +13 -0
- data/CONTRIBUTORS.md +17 -0
- data/README.md +267 -184
- data/lib/meta_events/definition/version.rb +21 -1
- data/lib/meta_events/tracker.rb +18 -12
- data/lib/meta_events/version.rb +1 -1
- data/spec/meta_events/definition/version_spec.rb +11 -0
- data/spec/meta_events/tracker_spec.rb +64 -6
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzJiMzIzNTY1ZGVhZTQwZjFmYWE1YWM5NTY3YzRhYjMyNDMyZDgwYw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Y2FkY2IxOTdhNTFmODVhMTQ1YTk1ODUxNzViNDIxNGNkOGFhMTk5ZQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MDA0ZjdjYWYwZTFiODRjZTdlNzk5YmJlOTc0ZGExZmQwOThlOWE0MWZjZWQ1
|
10
|
+
OTIzZjgxN2ZhY2M2OTM5YjVjMzUyY2I2NDE3ZWYwZDVjZTU0NmM1MTAyMWYx
|
11
|
+
NjBiNDFmMjhmMTljM2I5OTQ0ODRlNjA0MmIwZTA4MjljYmM5YjQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTU5Y2NlMmIyZWUxODIzODI4ZWMzZTk2OTM3ZTViMmY1OGM4MTBkYjU5OGEy
|
14
|
+
MjkzMmQwZjBkMWM3Y2ZjOTM4MmJiNWZiMGI0YWY3MjE5ZmJkMDk2NzdmNDgy
|
15
|
+
MTkwYzBmYThlNGQ4MDZiMTcyYjRhYjBiYzdhNmE0ZmNhZDczYWE=
|
data/.travis.yml
CHANGED
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.
|
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
|
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
|
-
|
63
|
+
```ruby
|
64
|
+
global_events_prefix :ab
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
198
|
+
```html+erb
|
199
|
+
<%= link_to("go here", user_awesome_path, :class => "my_class") %>
|
200
|
+
```
|
190
201
|
|
191
202
|
...to:
|
192
203
|
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
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
|
-
|
208
|
-
|
222
|
+
```html
|
223
|
+
<a href="/users/awesome" class="my_class mejtp_trk" data-mejtp-event="ab1_user_awesome"
|
224
|
+
data-mejtp-prp="{"ip":"127.0.0.1","color":"green","implicit_prop_1":"someValue"}">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
|
-
|
234
|
+
```javascript
|
235
|
+
//= require meta_events
|
236
|
+
```
|
218
237
|
|
219
238
|
And, finally, call something like this:
|
220
239
|
|
221
|
-
|
222
|
-
|
223
|
-
|
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 — 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 — at the bottom of the page is
|
249
270
|
perfectly fine:
|
250
271
|
|
251
|
-
|
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
|
-
|
281
|
+
```ruby
|
282
|
+
global_events_prefix :ab
|
259
283
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
311
|
+
```javascript
|
312
|
+
if (wonGame) {
|
313
|
+
MetaEvents.event('jsgame_won');
|
314
|
+
} else {
|
315
|
+
MetaEvents.event('jsgame_lost');
|
316
|
+
}
|
289
317
|
|
290
|
-
|
291
|
-
|
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
|
-
|
308
|
-
|
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
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
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
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
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 — 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
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
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
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
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 — tah-dah! — 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
|
-
|
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 — 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
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
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
|
-
|
568
|
+
MetaEvents::Tracker.default_event_receivers << MyEventReceiver.new
|
569
|
+
```
|
526
570
|
|
527
571
|
Voilà — 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
|
-
|
598
|
+
```ruby
|
599
|
+
global_events_prefix :ab
|
555
600
|
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
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
|
-
|
621
|
+
```ruby
|
622
|
+
global_events_prefix :ab
|
576
623
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
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
|
-
|
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
|
-
|
643
|
-
|
644
|
-
|
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
|
-
|
701
|
+
```ruby
|
702
|
+
global_events_prefix :ab
|
650
703
|
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
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_ — 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}>"
|
data/lib/meta_events/tracker.rb
CHANGED
@@ -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?
|
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,
|
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,
|
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.
|
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 }
|
data/lib/meta_events/version.rb
CHANGED
@@ -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({ '
|
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({ '
|
549
|
-
expect(expand(:baz => tep_object({ :foo => { :bar => ' yo yo yo '} }))).to eq({ '
|
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({ '
|
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(:
|
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.
|
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-
|
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.
|
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
|