- Notifications
You must be signed in to change notification settings - Fork31
Structured, documented, powerful event emitting library for Mixpanel and other such systems
License
swiftype/meta_events
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
MetaEvents
is a Ruby gem that sits on top of a user-centric analytics system likeMixpanel and provides structure, documentation, and a historical record to events,and a powerful properties system that makes it easy to pass large numbers of consistent properties with your events.
MetaEvents supports:
- 1.9.3, 2.0.0, 2.1.2, or JRuby 1.7.12
These are, however, just the versions it's tested against; MetaEvents contains no code that should be at allparticularly dependent on exact Ruby versions, and should be compatible with a broad set of versions.
Brought to you by the folks atSwiftype. First version written byAndrew Geweke. For additional contributors, seeCONTRIBUTORS.
If you're in a project usingBundler — for example, any Rails project, most Gems, andprobably most other Ruby software these days — and therefore have aGemfile
,simply add this to the end of yourGemfile
:
gem'meta_events'
Alternatively, if you aren't using Bundler:
geminstallmeta_events
Sending user-centric events to (e.g.) Mixpanel is far from difficult; it's a single method call. However, in alarge project, adding calls to Mixpanel all over eventually starts causing issues:
- Understanding what, exactly, an event is tracking, including when it was introduced and when it was changed, isparamount to doing correct analysis. But once events have been around for a while, whoever put them there haslong-since forgotten (or they may even not be around any more), and trying to understand what
User Upgraded Account
means, eighteen months later, involves an awful lot of spelunking. (Why did it suddenly triple, permanently, onFebruary 19th? Is that because we changed what the event means or because we improved the product?) - Getting a holistic view of what events there are and how they interact becomes basically impossible; all you can dois look at the output (i.e., Mixpanel) and hope you can put the pieces together from there.
- Critical to using Mixpanel well is to pass lots and lots of properties; engineers being the lazy folks that we are,we often don't do this, and, when we do, they're named inconsistently and may mean different things in differentplaces.
- Often you want certain properties of the currently-logged-in user (for example) passed on every single event, andthere's not always a clean way to do this.
MetaEvents
helps solve this problem by adding a few critical features:
- TheMetaEvents DSL requires developers to declare and document events as they add them (and if they don't, theycan't fire them); this is quick and easy, but enormously powerful as it gives you a holistic view of your events, ahistorical record, and detailed documentation on each one.
- Object properties support means you can define the set of event properties an object in your system (like aUser) should expose, and then simply pass that object in your event — this makes it vastly easier to includelots of properties, and be consistent about them.
- Implicit properties support means you can add contextual properties (like the currently-logged-in user) in asingle place, and then have every event include those properties.
- Front-end integration lets you very easily track events from DOM elements (like links) using JavaScript, anduse a powerful mechanism to fire front-end events in any way you want.
Let's get started. We'll assume we're working in a Rails project, although MetaEvents has no dependencies on Rails or any other particular framework. We'll also assume you've installed the MetaEvents gem (ideally via yourGemfile
).
Note: You will also need to provide whatever API your analytics system uses; for example, in the case of Mixpanel,you must also addgem mixpanel-ruby
to yourGemfile
.
First, let's declare an event that we want to fire. Createconfig/meta_events.rb
(MetaEvents automaticallyconfigures this as your events file if you're using Rails; if not, useMetaEvents::Tracker.default_definitions =
toset the path to whatever file you like):
global_events_prefix:abversion1,"2014-02-04"docategory:userdoevent:signed_up,"2014-02-04","user creates a brand-new account"endend
Let's walk through this:
global_events_prefix
is a short string that gets added before every single event; this helps discriminate eventscoming from MetaEvents from events coming from other systems. Choose this carefully, don't ever change it, and keepit short — most tools, like Mixpanel, have limited screen real estate for displaying event names.version 1
defines a version ofyour entire events system; this is useful in the case where you want to reworkthe entire set of events you fire — which is not an uncommon thing. But, for a while, we'll only need a singleversion, and we'll call it 1.2014-02-04
is when this version first was used; this can be any date (and time, if youreally want to be precise)that you want — it just has to be parseable by Ruby'sTime.parse
method. (MetaEvents never, ever comparesthis date toTime.now
or otherwise uses it; it's just for documentation.)category :user
is just a grouping and namespacing of events; the category name is included in every event namewhen fired.event :signed_up
declares an event with a name;2014-02-04
is required and is the date (and time) that thisevent was introduced. (Again, this is just for documentation purposes.)user creates a brand-new account
is alsojust for documentation purposes (and also is required), and describes the exact purpose of this event.
To fire an event, we need an instance ofMetaEvents::Tracker
. For reasons to be explained shortly, we'll want aninstance of this class to be created at a level where we may have things in common (like the current user) — so,in a Rails application, ourApplicationController
is a good place. We need to pass it thedistinct ID of the userthat's signed in, which is almost always just the primary key from theusers
table — ornil
if no user iscurrently signed in. We also pass it the IP address of the user (which can safely benil
); Mixpanel, for example,uses this for doing geolocation of users:
classApplicationController <ActionController::Base ...defmeta_events_tracker@meta_events_tracker ||=MetaEvents::Tracker.new(current_user.try(:id),request.remote_ip)end ...end
Now, from the controller, we can fire an event and pass a couple of properties:
classUsersController <ApplicationController ...defcreate ...meta_events_tracker.event!(:user,:signed_up,{:user_gender=>@new_user.gender,:user_age=>@new_user.age}) ...end ...end
We're just about all done; but, right now, the event isn't actually going anywhere, because we haven't configured anyevent receivers.
Anevent receiver is any object that responds to a method#track(distinct_id, event_name, event_properties)
, wheredistinct_id
is the distinct ID of the user,event_name
is aString
andevent_properties
is a Hash mappingString
property names to simple scalar values —true
,false
,nil
, numbers (allNumeric
s, including bothintegers and floating-point numbers, are supported),String
s (andSymbol
s will be converted toString
stransparently), andTime
objects.
Fortunately, theMixpanel Gem complies with this interface perfectly.So, inconfig/environments/production.rb
(or any other file that loads before your first event gets fired):
MetaEvents::Tracker.default_event_receivers <<Mixpanel::Tracker.new("0123456789abcdef")
(where0123456789abcdef
is actually your Mixpanel API token)
In our development environment, we may or may not want to include Mixpanel itself (so we can either add or not add theMixpanel event receiver, above); however, we might also want to print events to the console or some other file asthey are fired. So, inconfig/environments/development.rb
:
MetaEvents::Tracker.default_event_receivers <<MetaEvents::TestReceiver.new
This will print events as they are fired to your Rails log (e.g.,log/development.log
); you can pass an argumentto the constructor ofTestReceiver
that's aLogger
, anIO
(e.g.,STDOUT
,STDERR
, an openFile
object),or a block (or anything responding tocall
), if you want it to go elsewhere.
Now, when you fire an event, you should get output like this in your Rails log:
Tracked event: user 483123, "ab1_user_signed_up" user_age: 27 user_gender: female
...and, if you have configured Mixpanel properly, it will have been sent to Mixpanel, too!
Generally speaking, firing events from the back end (your application server talking to Mixpanel or some other servicedirectly) is more reliable, while firing events from the front end (JavaScript in your users' browsers talking toMixpanel or some other service) is more scalable — so you may wish to fire events from the front end, too.Further, there are certain events (scrolling, JavaScript manipulation in the browser, and so on) that simply don'texist on the back end and can't be tracked from there — at least, not without adding calls back to your serverfrom the front-end JavaScript.
IMPORTANT: In case it isn't obvious,any property you include in a front-end event is visible to your users.No matter what tricks you might include to obscure that data, it fundamentally will be present on your users' computersand thus visible to them if they want to take a look. This is no different than the situation would be withoutMetaEvents, but, because MetaEvents makes it so easy to add large amounts of properties (which is a good thing!),you should take extra care with your#to_event_properties
methods once you start firing front-end events.
You can fire front-end events with MetaEvents in two ways:auto-tracking andfrontend events. Both methods requirethe use of Rails (becauseMetaEvents::ControllerMethods
is intended for use withActionController
, andMetaEvents::Helpers
is intended for use withActionView
), although the techniques are generally applicable andeasy enough to use with any framework.
Note: Again, because MetaEvents does not directly require any one user-centric analytics system, you must make surethe JavaScript API to whatever system you're using is loaded, too. So, for example, if you're using Mixpanel, make surethe JavaScript code Mixpanel provides you to use its API is loaded on your pageas well as the MetaEvents JavaScriptcode.
Auto-tracking is the easiest way of triggering front-end events. MetaEvents provides a Rails helper method that addscertain attributes to any DOM element you wish (like a link); it then provides a JavaScript function that automaticallypicks up these attributes, decodes them, and calls any function you want with them.
As an example, in a view, you simply convert:
<%= link_to("go here", user_awesome_path, :class => "my_class")%>
...to:
<%= meta_events_tracked_link_to("go here", user_awesome_path, :class => "my_class", :meta_event => { :category => :user, :event => :awesome, :properties => { :color => 'green' } })%>
(Not immediately obvious: the:meta_event
attribute is just part of thehtml_options
Hash
thatlink_to
accepts, not an additional parameter.meta_events_tracked_link_to
accepts exactly the same parameters aslink_to
.)
This automatically turns the generated HTML from:
<ahref="/users/awesome"class="my_class">go here</a>
to something like this:
<ahref="/users/awesome"class="my_class mejtp_trk"data-mejtp-event="ab1_user_awesome"data-mejtp-prp="{"ip":"127.0.0.1","color":"green","implicit_prop_1":"someValue"}">go here</a>
mejtp
stands for "MetaEvents JavaScript Tracking Prefix", and is simply a likely-unique prefix for these values.(You can change it withMetaEvents::Helpers.meta_events_javascript_tracking_prefix 'foo'
.)mejtp_trk
is the classthat allows us to easily detect which elements are set up for tracking; the two data attributes pass the full nameof the event, and a JSON-encoded string of all the properties (both implicit and explicit) to pass with the event.
Now, add this to a Javascript file in your application:
//= require meta_events
And, finally, call something like this:
$(document).ready(function(){MetaEvents.forAllTrackableElements(document,function(id,element,eventName,properties){mixpanel.track_links("#"+id,eventName,properties);})});
MetaEvents.forAllTrackableElements
accepts a root element to start searching at, and a callback function. It findsall elements with classmejtp_trk
on them underneath that element, extracts the event name and properties, and addsa generated DOM ID to that element if it doesn't have one already. It then calls your callback function, passing that(existing or generated) DOM ID, the element itself, the name of the event, and the full set of properties (decoded, asa JavaScript Object here). You can then (as above) easily use this to do anything you want, like telling Mixpanel totrack that link properly.
forAllTrackableElements
also sets a certain data attribute on each element as it processes it, and knows to skipelements that already have that attribute set, so it's safe to call as often as you wish — for example, ifthe DOM changes. It doesnot know when the DOM changes, however, so, if you add content to your page, you willneed to re-call it.
Use Frontend Events only if Auto-Tracking isn't flexible enough for your purposes; Auto-Tracking is simpler inmost ways.
Because MetaEvents leverages the events DSL to define events, and calls methods on your Ruby models (and other objects)to create large numbers of properties, you cannot simply fire an event by name from the front-end without alittleextra work — otherwise, how would we get those properties? However, it's not much more work.
First off, make sure you get this into your layout in a<script>
tag somewhere — at the bottom of the page isperfectly fine:
<%= meta_events_frontend_events_javascript%>
This allows MetaEvents to pass event data properly from the backend to the frontend for any events you'll be firing.
Now, as an example, let's imagine we implement a JavaScript game on our site, and want to fire events when the userwins, loses, or gets a new high score. First, let's define those in our DSL:
global_events_prefix:abversion1,"2014-02-11"docategory:jsgamedoevent:won,"2014-02-11","user won a game!"event:lost,"2014-02-11","user lost a game"event:new_high_score,"2014-02-11","user got a new high score"endend
Now, in whatever controller action renders the page that the game is on, we need toregister these events. Thistells the front-end integration that we might fire them from the resulting page; it therefore embeds JavaScript in thepage that defines the set of properties for those events, so that the front end has access to the data it needs:
classGameController <ApplicationControllerdefgame ...meta_events_define_frontend_event(:jsgame,:won,{:winning_streak=>current_winning_streak})meta_events_define_frontend_event(:jsgame,:lost,{:losing_streak=>current_losing_streak})meta_events_define_frontend_event(:jsgame,:new_high_score,{:previous_high_score=>current_high_score}) ...endend
This will allow us to make the following calls in the frontend, from our game code:
if(wonGame){MetaEvents.event('jsgame_won');}else{MetaEvents.event('jsgame_lost');}if(currentScore>highScore){MetaEvents.event('jsgame_new_high_score',{score:currentScore});}
What's happened here is thatmeta_events_define_frontend_event
took the set of properties you passed, merged themwith any implicit properties defined, and passed them to the frontend via themeta_events_frontend_events_javascript
output we added above. It binds each event to anevent alias, which, by default, is just the category name and theevent name, joined with an underscore. So when you callMetaEvents.event
, it simply takes the string you pass it,looks up the event stored under that alias, merges any properties you supply with the ones passed from the backend,and fires it off. (You can, in fact, supply as many additional JavaScript objects/hashes as you want after theevent alias; they will all be merged together, along with the properties supplied by the backend.)
If you need to be able to fire the exact same event withdifferent sets of properties from different places in asingle page, you can alias the event using the:name
property:
classGameController <ApplicationControllerdefgame ...meta_events_define_frontend_event(:jsgame,:paused_game,{:while=>:winning},{:name=>:paused_while_winning})meta_events_define_frontend_event(:jsgame,:paused_game,{:while=>:losing},{:name=>:paused_while_losing}) ...endend
...if(winning){MetaEvents.event('paused_while_winning');}else{MetaEvents.event('paused_while_losing');}
Both calls from the JavaScript will fire the eventab1_jsgame_paused_game
, but one of them will passwhile: 'winning'
, and the otherwhile: 'losing'
.
Calls tometa_events_define_frontend_event
get aggregated on the current controller object, during the requestcycle. If you have events that can get fired on any page, then, for example, use abefore_filter
to alwaysdefine them, or a method you mix in and call, or any other mechanism you want.
MetaEvents.event
calls the currentfrontend event handler on theMetaEvents
JavaScript object; by default thisjust callsmixpanel.track
. By callingMetaEvents.setEventHandler(myFunction)
, you can set it to anything you want;it gets passed the fully-qualified event name and set of all properties.
We glossed over the discussion of the distinct ID above. In short, it is a unique identifier (of no particular format;both Strings and integers are acceptable) that is unique to the user in question, based on your application'sdefinition of 'user'. Using the primary key from yourusers
table is typically a great way to do it.
There are a few situations where you need to take special care, however:
- What about visitors who aren't signed in yet? In this case, you will want to generate a unique ID and assign itto the visitor anyway; generating a very large random number and putting it in a cookie in their browser is a goodway to do this, as well as using something like nginx's
ngx_http_userid_module
.(Note that Mixpanel has facilities to do this automatically; however, it uses cookies set on theirdomain, which means you can't read them, which limits it unacceptably — server-side code and even your ownJavascript will be unable to use this ID.) If you do this, take a look atweb_server_uid
, a gem that makes manipulating the resulting IDs vastly easier in Ruby. - What do I do when a user logs in? Typically, you simply want to switch completely from using their old(cookie-based) unique ID to using the primary key of your
users
table (or whatever you use for tracking logged-inusers). This may seem counterintuitive, but it makes sense, particularly in broad consumer applications: untilsomeone logs in, all you really know is whichbrowser is hitting your site, not whichuser. Activity that happensin the signed-out state might be the user who eventually logs in...but it also might not be, in the case of sharedmachines; further, activity that happens before the user logs in is unlikely to be particularly interesting to you— you already have the user as a registered user, and so this isn't a conversion or sign-up funnel. Effectivelytreating the activity that happens before they sign in as a completely separate user is actually exactly the rightthing to do. The correct code structure is simply to call#distinct_id=
on yourMetaEvents::Tracker
at exactlythe point at which you log them in (using your session, or a cookie, or whatever), and be done with it. - What do I do when a user signs up? This is the tricky case. You really want to correlate all the activity thathappened before the signup process with the activity afterwards, so that you can start seeing things like "users whocome in through funnel X convert to truly active/paid/whatever users at a higher rate than those through funnel Y".This requires support from your back-end analytics provider; Mixpanel calls italiasing, and it's accessed viatheir
alias
call. It effectively says "the user with autogenerated ID X is the exact same user as the user withprimary-key ID Y". Making this call is beyond the scope of MetaEvents, but is quite easy to do assuming youranalytics provider supports it.
You may also wish to see Mixpanel's documentation about distinct ID,here,here, andhere.
Now that we've gotten the basics out of the way, we can start using the real power of MetaEvents.
Very often, just by being in some particular part of code, you already know a fair amount of data that you want topass as events. For example, if you're inside a Rails controller action, and you have a current user, you're probablygoing to want to pass properties about that user to any event that happens in the controller action.
You could add these to every single call to#event!
, but MetaEvents has a better way. When you create theMetaEvents::Tracker
instance, you can defineimplicit properties. Let's add some now:
classApplicationController <ActionController::Base ...defmeta_events_trackerimplicit_properties={}ifcurrent_userimplicit_properties.merge!(:user_gender=>current_user.gender,:user_age=>current_user.age)end@meta_events_tracker ||=MetaEvents::Tracker.new(current_user.try(:id),request.remote_ip,:implicit_properties=>implicit_properties)end ...end
Now, these properties will get passed on every event fired by this Tracker. (This is, in fact, the biggestconsideration when deciding when and where you'll create newMetaEvents::Tracker
instances: implicit properties areextremely useful, so you'll want the lifecycle of a Tracker to match closely the lifecycle of something in yourapplication that has implicit properties.)
We're also going to face another problem: many events involve multiple underlying objects, each of which has manyproperties that are defined on it. For example, imagine we have an event triggered when a user sends a message toanother user. We have at least three entities: the 'from' user, the 'to' user, and the message itself. If we reallywant to instrument this event properly, we're going to want something like this:
meta_events_tracker.event!(:user,:sent_message,{:from_user_country=>from_user.country,:from_user_state=>from_user.state,:from_user_postcode=>from_user.postcode,:from_user_city=>from_user.city,:from_user_language=>from_user.language,:from_user_referred_from=>from_user.referred_from,:from_user_gender=>from_user.gender,:from_user_age=>from_user.age,:to_user_country=>to_user.country,:to_user_state=>to_user.state,:to_user_postcode=>to_user.postcode,:to_user_city=>to_user.city,:to_user_language=>to_user.language,:to_user_referred_from=>to_user.referred_from,:to_user_gender=>to_user.gender,:to_user_age=>to_user.age,:message_sent_at=>message.sent_at,:message_type=>message.type,:message_length=>message.length,:message_language=>message.language,:message_attachments=>message.attachments?})
Needless to say, this kind of sucks. Either we're going to end up with a ton of duplicate, unmaintainable code, orwe'll just cut back and only pass a few properties — greatly reducing the possibilities of our analyticssystem.
We can improve this situation by using a feature of MetaEvents: when properties are nested in sub-hashes, they getautomatically expanded and their names prefixed by the outer hash key. So let's define a couple of methods on models:
classUser <ActiveRecord::Basedefto_event_properties{:country=>country,:state=>state,:postcode=>postcode,:city=>city,:language=>language,:referred_from=>referred_from,:gender=>gender,:age=>age}endendclassMessage <ActiveRecord::Basedefto_event_properties{:sent_at=>sent_at,:type=>type,:length=>length,:language=>language,:attachments=>attachments?}endend
Now, we can pass the exact same set of properties as the above example, by simply doing:
meta_events_tracker.event!(:user,:sent_message,{:from_user=>from_user.to_event_properties,:to_user=>to_user.to_event_properties,:message=>message.to_event_properties})
SO much better.
And — tah-dah! — MetaEvents supports this syntax automatically. If you pass an object as a property, andthat object defines a method called#to_event_properties
, then it will be called automatically, and replaced.Our code now looks like:
meta_events_tracker.event!(:user,:sent_message,{:from_user=>from_user,:to_user=>to_user,:message=>message})
To make the most use of MetaEvents, define#to_event_properties
very liberally on objects in your system, make themreturn any properties you even think might be useful, and pass them to events. MetaEvents will expand them for you,allowing large numbers of properties on events, which allows Mixpanel and other such systems to be of the most useto you.
A few things before we're done:
When deploying Mixpanel or other such systems in a live application, you very often want to dispatch event reportingin the background, so that any slowdown in Mixpanel's servers doesn't slow down your application (or, in the worstcase, bring it down entirely). Typically, this is done using something likeResque,Sidekiq, or another queue-based system. There are literally dozens of these systems availablefor Ruby, and it's highly likely that your codebase either uses one already or will soon. Additionally, many userslayer additional features like logging, durability, or other infrastructure services on top of the base functionalityof these packages.
Because there is such a wide variety of these systems available, MetaEvents does notdirectly provide support forthem — doing so would be a great deal of effort, and yet still unlikely to satisfy most users. Instead,MetaEvents makes it very easy to use any package you want:
classMyEventReceiverdeftrack(distinct_id,event_name,event_properties)# Call Resque, Sidekiq, or anything else you want here; enqueue a job that, when run, will call:# Mixpanel::Tracker.new($my_mixpanel_api_key).track(distinct_id, event_name, event_properties)endendMetaEvents::Tracker.default_event_receivers <<MyEventReceiver.new
Voilà — asynchronous event tracking.
MetaEvents isnot intended as a complete superset of a backend analytics library (like Mixpanel) — there arefeatures of those libraries that are not implemented via MetaEvents, and which should be used by direct calls to theservice in question.
For example, Mixpanel has analias
call that lets you tell it that a user with a particular distinct ID is actuallythe same person as a user with a different distinct ID — this is typically used at signup, when you convert froman "anonymous" distinct ID representing the unknown user who is poking around your site to the actual official user ID(typically yourusers
table primary key) of that user. MetaEvents does not, in any way, attempt to support this; itallows you to pass whateverdistinct_id
you want in the#event!
call, but, if you want to usealias
, you shouldmake that Mixpanel call directly. (See also the discussion above aboutdistinct ID.)
Similarly, Mixpanel's People functionality is not in any way directly supported by MetaEvents. You may well use theTracker's#effective_properties
method to compute a set of properties that you pass to Mixpanel's People system,but there are no calls directly in MetaEvents to do this for you.
Often you'll have events that youretire — they were used in the past, but no longer. You could just deletethem from your MetaEvents DSL file, but this will mean the historical record is suddenly gone. (Well, there's sourcecontrol, but that's a pain.)
Rather than doing this, you can retire them:
global_events_prefix:abversion1,"2014-02-04"docategory:userdoevent:logged_in_with_facebook,"2014-02-04","user creates a brand-new account",:retired_at=>"2014-06-01"event:signed_up,"2014-02-04","user creates a brand-new account"endend
Given the above, trying to callevent!(:user, :logged_in_with_facebook)
will fail with an exception, because theevent has been retired. (Note that, once again, the actual date passed to:retired_at
is simply for record-keepingpurposes; the exception is generated if:retired_at
is set toanything.)
You can retire events, categories, and entire versions; this system ensures the DSL continues to be a historical recordof what things were in the past, as well as what they are today.
You can also add notes to events. They must be tagged with the author and the time, and they can be very useful fordocumenting changes:
global_events_prefix:abversion1,"2014-02-04"docategory:userdoevent:signed_up,"2014-02-04","user creates a brand-new account"donote"2014-03-17","jsmith","Moved sign-up button to the home page -- should increase signups significantly"endendend
This allows you to record changes to events, as well as the events themselves.
Currently, the documentation for the MetaEvents DSL is the source to that DSL itself —i.e.,config/meta_events.rb
or something similar. However, methods on the DSL objects created (accessible viaaTracker
's#definitions
method, orMetaEvents::Tracker
'sdefault_definitions
class method) allow forintrospection, and could easily be extended to,e.g., generate HTML fully documenting the events.
Patches are welcome. ;-)
MetaEvents automatically adds atime
property to any event you fire via#event!
; this is so that you can take theset of properties in a receiver and make it asynchronous, and don't have to worry about getting the time right. Youcan override this, however, by simply passing a:time
property with your event; it will override any time we wouldotherwise set. (You can even set:time => nil
if you want to make sure no time is passed at all.)
MetaEvents correctly converts anyTime
object you pass into the correct String format for Mixpanel (e.g.,2014-02-03T15:49:17
), converting it to UTC first. This should make your times much cleaner.
What is this top-levelversion
in the DSL? Well, every once in a while, you will want to completely redo your set ofevents — perhaps you've learned a lot about using your analytics system, and realize you want them configuredin a different way.
When you want to do this, define a new top-levelversion
in your DSL, and pass:version => 2
(or whatever numberyou gave the new version) when creating yourMetaEvents::Tracker
. The tracker will look under that version forcategories and events, and completely ignore other versions; your events will be called things likeab2_user_signup
instead ofab1_user_signup
, and so on. The old version can still stay present in your DSL for documentation andhistorical purposes.
When you're completely done with the old version, retire it —version 1, :retired_at => '2014-06-01' do ...
.
Often, you'll want to run two versions simultaneously, because you want to have a transition period where you fireboth sets of events — this is hugely helpful in figuring out how your old events map to new events andwhen adjusting bases for the new events. (If you simply flash-cut from an old version to a new one on a single day,it is difficult or impossible to know if true underlying usage, etc.,actually changed, or if it's just an artifactof changing events.) You can simply create twoMetaEvents::Tracker
instances, one for each version, and use themin parallel.
Developers love names like "xyz1_user_signed_up" but sometimes it's not a developer doing the analysis.Depending on what the back-end analytics library supports, event names in external systems are frequently notgiven a lot of real estate.
In cases like these, you can override the default external event name behavior. There are three ways tooverride these external names.
First, you can override them globally for allMetaEvents::Tracker
instances:
MetaEvents::Tracker.default_external_name=lambda{ |event|"#{event.category_name}#{event.name}"}
Second, you can override them for a specificMetaEvents::Tracker
instance:
MetaEvents::Tracker.new(current_user.try(:id),request.remote_ip,:external_name=>lambda{ |event|"#{event.category_name}#{event.name}"})
Finally, you can override each event's external name in the events DSL:
global_events_prefix:abversion1,"2014-02-11"docategory:example_categorydoevent:example_event,"2014-02-11","Example was exampled!",:external_name=>'ex. was ex.'endend
The order of precedence for determining the external event name is the DSL'sevent :external_name => 'foo'
,MetaEvents::Tracker.new
,MetaEvents::Tracker.default_external_name
, built-in default.
Similarly, while developers might be perfectly comfortable with (and even prefer) expanded properties named thingslikeuser_age
,user_name
, and so on, others might want a different separator (like a space character). Whendefining a version, you can set this, as follows:
global_events_prefix:abversion1,"2014-02-11",:property_separator=>' 'docategory:example_categorydoevent:example_event,"2014-02-11","Example was exampled!"endend
Now, assuming@user
is aUser
object that responds to#to_event_properties
(or is just aHash
):
tracker.event!(:example_category,:example_event,:user=>@user)
...you'll get properties nameduser age
,user name
, and so on, rather thanuser_age
anduser_name
.
Note that this is defined on theversion
, not thecategory
,event
, or even#event!
call, because changingthis isa big deal — changing property names almost always breaks all kinds of analysis you might want to dowith your analytics tool. However, the idea is that changingversion
s is a breaking change to your analyticssystem anyway, so you can certainly set it on a newversion
to something different.
- Fork it (http://github.com/swiftype/meta_events/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
About
Structured, documented, powerful event emitting library for Mixpanel and other such systems