Last Updated: October 30, 2021
·
20.24K
· teropa

Track Server-Side Events from Rails in Google Analytics

Sometimes there doesn't seem to be a way to reliably and unobtrusively send a tracking event to Google Analytics. A typical case for this is when you need to track a form submit, after which the user will be redirected to some other system.

Alternatives

There are several approaches one could take to track a case like this:

  • You could track signups by triggering an event on the next page that follows a successful signup. But that might mean you need an extraneous "Welcome" or "Thank You" page interrupting the user flow just to get some analytics recorded.
  • You could trigger an analytics event with JavaScript when the user submits the form. The problem with this is that the event delivery in Google Analytics is asynchronous, so you cannot be sure if the event was really recorded before the user's browser left the page (without resorting to some iffy setTimeout() trickery).

If you do some research (AKA Googling) around this problem, you'll find recommendations for all of these methods. For me, none of them felt right.

Universal Analytics And The Measurement Protocol

What we really want to do is send the event to Google Analytics from the server, since that seems to be the only option that's both reliable and unobtrusive to the user.

This used to be quite tricky with Google Analytics, but now they have a proper solution for it in their new Universal Analytics traking (currently in public beta): The Measurement Protocol.

From our perspective, the Measurement Protocol is basically nothing but an HTTP endpoint that receives Google Analytics tracking data. We can call it from our server-side code as long as we have the Analytics account information available.

Here is how we set it up for our marketing site - and how you can set it up for your Rails app or site:

1. Set Up A Universal Google Analytics Property

Unless your Analytics web property is already uses the Universal Analytics tracking method, you'll need to create a new web property. Most old properties use Classic Analytics instead, and that's still the default for new properties. You can see the type by navigating to the property in GA Admin, and opening the Tracking Info tab. If it says something like "Universal Analytics have been enabled", you're good to go.

If you do need to create a new property, just make sure you've selected the Universal property column on the creation screen:

Picture

Note that you'll also need to update the Google Analytics JavaScript tracking code on all your pages, so that your site will start gathering analytics to this new profile.

2. Set Up Google Analytics Configuration for Your Rails Application

After Step 1, your normal client-side tracking should already be fully functional. The rest of the article will be about setting up the server-side tracking in your Rails application.

First of all, let's put the Google Analytics configuration in its own YAML configuration file:

production:
  endpoint: "http://www.google-analytics.com/collect"
  version: 1
  tracking_code: UA-12345678-9
  • We have one configuration entry per Rails environment. We're only really interested in production data so that's the only one we configure (you may also want to add a second entry for the development environment while you're setting this up).
  • We configure the Measurement Protocol endpoint and version, matching the values in the GA documentation
  • We define our Google Analytics tracking code. This should be the same as you have in your client-side JavaScript tracking snippet (the value beginning with UA).

Next, let's make a simple Rails initializer that makes this configuration available to our code at runtime:

GOOGLE_ANALYTICS_SETTINGS = HashWithIndifferentAccess.new

config = YAML.load_file(Rails.root.join("config", "google_analytics_settings.yml"))[Rails.env]
if config
  GOOGLE_ANALYTICS_SETTINGS.update(config)
end
  • Here, we make the settings available in a global constant. It is initialized as an ActiveSupport HashWithIndifferentAccess, which means we will be able to access its keys either as Symbols or as Strings.
  • We load the YAML config file and look up the environment configuration that matches the current Rails environment. If there is one, we update our constant hash with its contents.

3. Add A Library Class for Invoking The Measurement Protocol

We want event tracking to be as simple as possible from our application's point of view, so let's make a library class that encapsulates all the details.

What we want is a method that sends an event to Google Analytics. To make this as simple as possible, we're going to include no more than a couple of things in the event:

  • The event category and action. These specify what the event is about, and you can use them later to associate the event with measurement goals.
  • The client id associated with the event. This will let you connect the event to all the other data you have about the same user's actions.

See the GA documentation for other kinds of data you could associate with the event.

Let's define a simple class with a method that takes these arguments. The method invokes the Measurement Protocol API in order to record the event. Here we are using the rest_client Gem, which makes HTTP calls ridiculously easy. (Be sure to add it to your Gemfile if you want to use it.)

class GoogleAnalyticsApi

  def event(category, action, client_id = '555')
    return unless GOOGLE_ANALYTICS_SETTINGS[:tracking_code].present?

    params = {
      v: GOOGLE_ANALYTICS_SETTINGS[:version],
      tid: GOOGLE_ANALYTICS_SETTINGS[:tracking_code],
      cid: client_id
      t: "event",
      ec: category,
      ea: action
    }

    begin
      RestClient.get(GOOGLE_ANALYTICS_SETTINGS[:endpoint], params: params, timeout: 4, open_timeout: 4)
      return true
    rescue  RestClient::Exception => rex
      return false
    end
  end

end
  • Line 5: The client_id argument is optional, with a default value of 555. This is a convention we picked up from the documentation.
  • Line 6: We skip the tracking if there's no tracking code in the configuration (which will be the case in development and test environments).
  • Lines 8-15: We construct the parameters for the API call, based on the method arguments and the global Analytics configuration
  • Lines 17-22: Here we make the actual API call to the Measurement Protocol. Note how we also set some timeout values so that the request won't be left hanging for too long if there's a network problem. We ignore any problems raised (by just returning false), so that a failing analytics call won't disturb other application behavior.

4. Pass The Analytics Client Id to Your Rails Actions

Before we're ready to call this new class from our controllers, there's one piece of data we need to pass from the web browser: This is the client id. It is an identifier Google Analytics assigns to each of your visitors, so that it can track their behavior over time.

Including the client id isn't strictly required (that's why we defined the default value 555), but it is highly recommended, because otherwise you won't be able to associate the server-side events with any other data you have in Google Analytics.

First, in all the forms that execute actions that you want to track - in our case, our signup forms - include a hidden form field for the Analytics client id:

<%= hidden_field_tag 'ga_client_id', '', :class => 'ga-client-id' %>

The value of the field is empty at first, because we won't know it before the Analytics JavaScript library has loaded. Instead, we will populate it from JavaScript.

The following snippet assumes you're using jQuery. Put it in a JavaScript file (or simply in a <script> tag in your HTML) somewhere after the Google Analytics JavaScript snippet.

$(document).ready(function() {
  ga(function(tracker) {
    var clientId = tracker.get('clientId');
    $('.ga-client-id').val(clientId);
  });
});

Our code is inside two callbacks: The first one is jQuery's document.ready, making sure that our page has loaded, and the second one is from Google Analytics, making sure it has initialized. In the code we simply get the current client id from the Google Analytics library, and put it as the value of any of those hidden form fields we might have on the page (identified by the CSS class).

5. Call the Analytics Client to Trigger An Event

That's it for all the setup! Now all we need to do is call our Google Analytics client from anywhere we want to track an event from. For example, we have something like this in the controller that handles signups:

GoogleAnalyticsApi.new.signup('billing', 'signup', params[:ga_client_id])

Finally, it is highly recommended you tuck these calls away in a background job, over Resque or one of its alternatives, so that you don't make a potentially slow and unreliable HTTP call to an external service during request processing.

When you've set everything up, you should start seeing these events in your Google Analytics reports. You can actually track them in real time, by navigating to Real-time -> Events in the Google Analytics web interface. You'll be able to see signups - all of them - fly past as they happen!!

3 Responses
Add your response

I faced this same problem (activations not reliably being recorded), but with a different analytics platform.

I ended up writing a promise-based wrapper around the tracking method. The promise resolves when the tracker image finishes loading.

Used sparingly, this solution provides a synchronous hook into the analytics-tracking workflow and eliminates the unreliable use of setTimeout().

(I also wrote a custom pub/sub implementation that detects when promises are used by subscribers in order to conditionally provide promises on all .publish() calls. This means any subscriber can transparently inject dependencies into the pub/sub lifecycle on an as-needed, even ad-hoc, basis. All analytics tracking is abstracted using events. So, if we want to redirect, but only after all of an event's subscribers have finished responding to the event, all we have to do is wrap the redirect in the event's .done() method.)

over 1 year ago ·

I think you missed a comma in the event class after client_id and you also need to make the event class a Class method by using self.event

over 1 year ago ·

Also one can use gabba gem https://github.com/hybridgroup/gabba

over 1 year ago ·