At my current workplace, which is Kubicle, we wanted to integrate Mixpanel to the main Ruby on Rails application for better product analytics. This article is about how the integration work was started and how it was implemented.

Basically Mixpanel has events with custom names which can have custom property attributes and also relate to a user profile. Then on Mixpanel, you can create custom reports to analyse events based on properties, users etc.

We follow EPICs in our team. So this was also started as an EPIC and my manager already had started to look into Mixpanel as part of the project initialization and he found an online article that suggest an integration solutions for the Rails application.

Then the EPIC was handed to me to evaluate the approach or figure out a suitable approach and spike on having a prototype where we can start the work based on.

Investigation into Mixpanel

The suggested approach in the article was to hook into every Rails controller action and trigger an event to Mixpanel. Basically this is done by using ActiveSupport instrumentation hook process_action.action_controllerInfo.

The event name would simply be based on controller name and action name including any special router endpoint arguments as event property names.

However the problem with this is that the amount of different types of events that would get created on Mixpanel. Mixpanel recommends only to have a handful of events at least in the begenning.

Second problem was that how the product team would not know what condititions trigger the events as some controller names might not make sense in term of business domain.

So I suggested to have only specific events for specific useful business domain scenarios happens so that it is valuable insight for the product team. This way we have control of when and how the event is created.

So I discussed my suggestion and created two prototype versions. One with the controller instrumentation and one with specific event creation (e.g. movie watched). Then we ended up with a smart hybrid solution.

Implementation

Our manager wanted to capture page view events. Basically that means in our Rails application we have some front-end specific controllers that serve item listings (index action) item viewings (show action). For examples, list of courses and course contents. So in those situations we could also have the event trigerred inline in the controller however that will leave the code base populated with the page view event triggers. So here we could use the intrumentation approach to have the page view event trigerred from one place. To make the event easier to trigger, while giving more context to the event itself, we basically used the page URL path as an event property for Mixpanel.In Rails we can get this from request.fullpath method.

For the custom events for example lets say an event called ‘Movie Watched’, rather than directly trigerring the event using the Mixpanel Ruby SDK in the code inline, We created a namespaced classes that encapsulate the specific Mixpanel event name and allowed properties.

This way when we trigger the Mixpanel event, we wouldn’t end up with code statements that specify the event name as a string throughout the code base which reduce readability and increase the chance of typos.

Example event class

module Events
  module Mixpanel
    class MovieWatched < Base
      EVENT_NAME = 'Movie Watched'.freeze
      PROP_MOVIE_ID = 'Movie ID'.freeze

      def initialize(user:, movie_id:, request:, **kw_args)
        super(request: request, **kw_args)

        set_event_user(user)
        set_event_name(EVENT_NAME)
        set_properties({ PROP_MOVIE_ID => movie_id })
      end
    end
  end
end

In the initialize method we specify what keyword arguments are allowed.

Note that the these event classes are extended from a parent class called Base.

In the Base class we have two interesting methods, (apart from the setter methods).

One is the trigger method which simply triggers a job that will eventually send the event to Mixpanel.

Another method is the track class method which basically initializes the event object and calls trigger method. So its a short of a helper method when you just want to trigger a specific event without need to manually initialize and call trigger.

module Events
  module Mixpanel
    class Base
      def self.track(*args, **kw_args)
        new(*args, **kw_args).trigger
      end
      ...
      def trigger
        MixpanelUserEventTrackJob.perform_later(@user, @event_name, @properties, @ip)
      end
    end
  end
end

Example inline code that will call the event.

::Events::Mixpanel::MovieWatched.track(
  user: current_user,
  movie_id: movie.id,
  request: request
)

Takeaway

Code implementation wise, there could be small refactoring to do to make it more Rubyish but at a high level the design and implementation made the perfect balanced of the requirements while keeping sense of the code base.

For example, if we manually place code statements to track all places where page views occur, we will have to modify a lot of places in controller classes. So triggering the page views from one place makes sense.

On the other hand when we need to trigger events for specific conditions, it would be very difficult to do it in a single place using the same approach as Rails controller instrumentation. As we would end up with a lot of custom code to ensure the specific case is met before triggering the event. Also if a new developer comes and change the logic they would not know that it might impact the Mixpanel event code in this case which could break the event trigger mechanism. Therefore triggering the events explicity here makes sense as it’s more visible and easier to maintain.