meta_events 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: 
|
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
|