Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Updated gem to include Custom Tracking Variables and Events #9

Merged
merged 16 commits into from

4 participants

@smontgomerie
Collaborator

These commits add fully unit-tested code for adding custom variables and events to google analytics in async mode. Usage is described in the README.

Essentially it allows users to specify custom variables and events in their application controllers. It accomplishes this by adding instance methods to the ActionController::Base class. These methods store data in the environment to pass to rack, and write the variables when rendering the GA javascript tags.

The documentation for Google Analytics tracking requirements is detailed here:
https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables
https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide

Open to discussion on code style. Particularly, I'm not sure I like this syntax:

    set_ga_custom_var(1, "LoggedIn", value, GoogleAnalytics::CustomVar::SESSION_LEVEL)

I might prefer to modify the method to take a hash:

    set_ga_custom_var( :slot => 1, :name => "LoggedIn", :value => value, :scope => GoogleAnalytics::CustomVar::SESSION_LEVEL)
@leehambley

@smontgomerie added you as a contributor, you should be able to push upstream now.

In the meantime that should do - I will add you to rubygems.org too as soon as figure out how (if you have prior experience please let me know) I'm going on vacation in about 14 hours, and I need to sleep between now and then, so i'm a little tight on time :zzz:

@smontgomerie
Collaborator

Great, thanks! If you have any comments on the code I'd love your feedback. Have a great vacation!

@anicholson
Collaborator

@smontgomerie @leehambley hi guys, looking to use this middleware in a project, but I'd need this PR for event tracking. Is that going to be merged any time soon?

Cheers :)

@leehambley

This pull request cannot be automatically merged.

I've given you commit access, when you've resolved the changes, I'll be happy to push a release to rubygems.org

@smontgomerie smontgomerie Merge branch 'master' of https://github.com/leehambley/rack-google-an…
…alytics

Conflicts:
	lib/rack/google-analytics.rb
	test/helper.rb

All specs pass
01b4a3a
@smontgomerie smontgomerie merged commit 1191f0e into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 6, 2012
  1. @smontgomerie
  2. @smontgomerie
  3. @smontgomerie

    Update master

    smontgomerie authored
Commits on Jul 10, 2012
  1. @smontgomerie
  2. @smontgomerie
  3. @smontgomerie

    Update master

    smontgomerie authored
  4. @smontgomerie

    Refactor

    smontgomerie authored
  5. @smontgomerie

    Added unit tests

    smontgomerie authored
  6. @smontgomerie
  7. @smontgomerie
  8. @smontgomerie
  9. @smontgomerie

    Fixed unit tests

    smontgomerie authored
  10. @smontgomerie
Commits on Sep 25, 2012
  1. @salrepe

    changed order of tracker_vars

    salrepe authored
  2. @smontgomerie

    Merge pull request #1 from salrepe/master

    smontgomerie authored
    Changed tracked vars order
Commits on Mar 20, 2013
  1. @smontgomerie

    Merge branch 'master' of https://github.com/leehambley/rack-google-an…

    smontgomerie authored
    …alytics
    
    Conflicts:
    	lib/rack/google-analytics.rb
    	test/helper.rb
    
    All specs pass
This page is out of date. Refresh to see the latest.
View
20 README.md
@@ -48,6 +48,26 @@ use Rack::GoogleAnalytics, :tracker => 'UA-xxxxxx-x', :async => false
If you are not sure what's best, go with the defaults, and read here if you should opt-out.
+## Custom Variable Tracking
+
+** Added in this fork only **
+
+In your application controller, you may track a custom variable. For example:
+
+ set_ga_custom_var(1, "LoggedIn", value, GoogleAnalytics::CustomVar::SESSION_LEVEL)
+
+See https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables for details.
+
+## Event Tracking
+
+** Added in this fork only **
+
+In your application controller, you may track an event. For example:
+
+ track_ga_event("Users", "Login", "Standard")
+
+See https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide
+
## Thread Safety
This middleware *should* be thread safe. Although my experience in such areas is limited, having taken the advice of those with more experience; I defer the call to a shallow copy of the environment, if this is of consequence to you please review the implementation.
View
9 lib/google-analytics.rb
@@ -0,0 +1,9 @@
+require "active_support/json"
+
+require "rack/google-analytics"
+require "tracking/custom_var"
+require "tracking/event"
+
+require "google-analytics/instance_methods"
+
+ActionController::Base.send(:include, GoogleAnalytics::InstanceMethods) if defined?(ActionController::Base)
View
49 lib/google-analytics/instance_methods.rb
@@ -0,0 +1,49 @@
+# This module holds all instance methods to be
+# included into ActionController::Base class
+# for enabling google analytics var tracking in a Rails app.
+#
+require "erb"
+
+module GoogleAnalytics
+ module InstanceMethods
+
+ private
+
+ def ga_custom_vars
+ self.env["google_analytics.custom_vars"] ||= []
+ end
+
+ def ga_events
+ self.env["google_analytics.event_tracking"] ||= []
+ end
+
+ protected
+
+ # Sets a custom variable on a page load
+ #
+ # e.g. writes
+ # _gaq.push(['_setCustomVar',
+ # 2, // This custom var is set to slot #2. Required parameter.
+ # 'Shopping Attempts', // The name of the custom variable. Required parameter.
+ # 'Yes', // The value of the custom variable. Required parameter.
+ # // (you might set this value by default to No)
+ # 2 // Sets the scope to session-level. Optional parameter.
+ # ]);
+ def set_ga_custom_var(slot, name, value, scope = nil)
+ var = GoogleAnalytics::CustomVar.new(slot, name, value, scope)
+
+ ga_custom_vars.push(var)
+ end
+
+ # Tracks an event or goal on a page load
+ #
+ # e.g. writes
+ # _gaq.push(['_trackEvent', 'Videos', 'Play', 'Gone With the Wind']);
+ #
+ def track_ga_event(category, action, label = nil, value = nil, noninteraction = nil)
+ var = GoogleAnalytics::Event.new(category, action, label, value, noninteraction)
+ ga_events.push(var)
+ end
+
+ end
+end
View
22 lib/rack/google-analytics.rb
@@ -5,6 +5,8 @@ module Rack
class GoogleAnalytics
+ EVENT_TRACKING_KEY = "google_analytics.event_tracking"
+
DEFAULT = { :async => true }
def initialize(app, options = {})
@@ -18,9 +20,24 @@ def _call(env)
@status, @headers, @body = @app.call(env)
return [@status, @headers, @body] unless html?
response = Rack::Response.new([], @status, @headers)
- @body.each { |fragment| response.write inject(fragment) }
- @body.close if @body.respond_to?(:close)
+ @options[:tracker_vars] = env["google_analytics.custom_vars"] || []
+
+ if response.ok?
+ # Write out the events now
+ @options[:tracker_vars] += (env[EVENT_TRACKING_KEY]) unless env[EVENT_TRACKING_KEY].nil?
+
+ # Get any stored events from a redirection
+ session = env["rack.session"]
+ stored_events = session.delete(EVENT_TRACKING_KEY) if session
+ @options[:tracker_vars] += stored_events unless stored_events.nil?
+ elsif response.redirection?
+ # Store the events until next time
+ env["rack.session"][EVENT_TRACKING_KEY] = env[EVENT_TRACKING_KEY]
+ end
+ @response.each { |fragment| response.write inject(fragment) }
+ @body.close if @body.respond_to?(:close)
+
response.finish
end
@@ -30,6 +47,7 @@ def html?; @headers['Content-Type'] =~ /html/; end
def inject(response)
file = @options[:async] ? 'async' : 'sync'
+
@template ||= ::ERB.new ::File.read ::File.expand_path("../templates/#{file}.erb",__FILE__)
if @options[:async]
response.gsub(%r{</head>}, @template.result(binding) + "</head>")
View
7 lib/rack/templates/async.erb
@@ -9,6 +9,13 @@
_gaq.push(['_setDomainName', 'none']);
_gaq.push(['_setAllowLinker', true]);
<% end %>
+<% if @options[:tracker_vars]
+ @options[:tracker_vars].each do |var|
+%>
+ _gaq.push(<%= var.write() %>);
+<% end
+ end
+ %>
_gaq.push(['_trackPageview']);
_gaq.push(['_trackPageLoadTime']);
View
14 lib/tracking/custom_var.rb
@@ -0,0 +1,14 @@
+module GoogleAnalytics
+
+ # A Struct that mirrors the structure of a custom var defined in Google Analytics
+ # see https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables
+ class CustomVar < Struct.new(:index, :name, :value, :opt_scope)
+ VISITOR_LEVEL = 1
+ SESSION_LEVEL = 2
+ PAGE_LEVEL = 3
+
+ def write
+ ['_setCustomVar', self.index, self.name, self.value,self.opt_scope].to_json
+ end
+ end
+end
View
13 lib/tracking/event.rb
@@ -0,0 +1,13 @@
+require "active_support/json"
+
+module GoogleAnalytics
+
+ # A Struct that mirrors the structure of a custom var defined in Google Analytics
+ # see https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide
+ class Event < Struct.new(:category, :action, :label, :value, :noninteraction)
+
+ def write
+ ['_trackEvent', self.category, self.action, self.label,self.value, self.noninteraction].to_json
+ end
+ end
+end
View
32 test/helper.rb
@@ -2,6 +2,10 @@
require 'test/unit'
require 'shoulda'
require 'rack/test'
+require 'active_support/core_ext/hash/slice'
+require File.expand_path('../../lib/rack/google-analytics',__FILE__)
+require File.expand_path('../../lib/tracking/custom_var',__FILE__)
+require File.expand_path('../../lib/tracking/event',__FILE__)
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -12,20 +16,28 @@ class Test::Unit::TestCase
def app; Rack::Lint.new(@app); end
+ def main_app(options)
+ lambda { |env|
+
+ env["google_analytics.event_tracking"] = options[:events] if options[:events]
+ env["google_analytics.custom_vars"] = options[:custom_vars] if options[:custom_vars]
+
+ request = Rack::Request.new(env)
+ case request.path
+ when '/' then [200,{ 'Content-Type' => 'application/html' },['<head>Hello world</head>']]
+ when '/test.xml' then [200,{'Content-Type' => 'application/xml'}, ['Xml here']]
+ when '/bob' then [200,{'Content-Type' => 'application/html'} ,['<body>bob here</body>']]
+ else [404,'Nothing here']
+ end
+ }
+ end
+
def mock_app(options)
- main_app = lambda { |env|
- request = Rack::Request.new(env)
- case request.path
- when '/' then [200,{ 'Content-Type' => 'application/html' },['<head>Hello world</head>']]
- when '/test.xml' then [200,{'Content-Type' => 'application/xml'}, ['Xml here']]
- when '/bob' then [200,{'Content-Type' => 'application/html'} ,['<body>bob here</body>']]
- else [404,'Nothing here']
- end
- }
+ app_options = options.slice(:events, :custom_vars )
builder = Rack::Builder.new
builder.use Rack::GoogleAnalytics, options
- builder.run main_app
+ builder.run main_app(app_options)
@app = builder.to_app
end
end
View
63 test/test_rack-google-analytics-events.rb
@@ -0,0 +1,63 @@
+require File.expand_path('../helper',__FILE__)
+
+class TestRackGoogleAnalyticsEvents < Test::Unit::TestCase
+
+ context "Asyncronous With Events" do
+ context "default" do
+ setup do
+ events = [GoogleAnalytics::Event.new("Users", "Login", "Standard")]
+ mock_app :async => true, :tracker => 'somebody', :events => events
+ end
+ should "show events" do
+ get "/"
+
+ assert_match %r{\_gaq\.push}, last_response.body
+ assert_match %r{_trackEvent.*_trackPageview}m, last_response.body
+ assert_match %r{Users}, last_response.body
+ assert_match %r{Login}, last_response.body
+ assert_match %r{Standard}, last_response.body
+ end
+
+ end
+ end
+
+ context "Asyncronous With Custom Vars" do
+ context "default" do
+ setup do
+ custom_vars = [GoogleAnalytics::CustomVar.new(1, "Items Removed", "Yes", GoogleAnalytics::CustomVar::SESSION_LEVEL)]
+ mock_app :async => true, :tracker => 'somebody', :custom_vars => custom_vars
+ end
+ should "show events" do
+ get "/"
+
+ assert_match %r{\_gaq\.push}, last_response.body
+ assert_match %r{_setCustomVar.*_trackPageview}m, last_response.body
+ assert_match %r{Items Removed}, last_response.body
+ assert_match %r{Yes}, last_response.body
+ end
+
+ end
+ end
+
+ context "Test Instance Methods" do
+ context "default" do
+ setup do
+ custom_vars = [GoogleAnalytics::CustomVar.new(1, "Items Removed", "Yes", GoogleAnalytics::CustomVar::SESSION_LEVEL)]
+ mock_app :async => true, :tracker => 'somebody', :custom_vars => custom_vars
+ end
+ should "show events" do
+# controller.set_ga_custom_var(GoogleAnalytics::CustomVar.new(1, "Items Removed", "Yes", GoogleAnalytics::CustomVar::SESSION_LEVEL))
+
+ get "/"
+
+ assert_match %r{\_gaq\.push}, last_response.body
+ assert_match %r{_setCustomVar.*_trackPageview}m, last_response.body
+ assert_match %r{Items Removed}, last_response.body
+ assert_match %r{Yes}, last_response.body
+ end
+
+ end
+ end
+
+
+end
View
69 test/test_rack-google-analytics-instance-methods.rb
@@ -0,0 +1,69 @@
+$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
+
+require 'rubygems'
+require 'test/unit'
+require 'shoulda'
+require 'rack'
+require 'rack/test'
+require 'active_support/core_ext/hash/slice'
+require "action_controller"
+require File.expand_path('../../lib/google-analytics', __FILE__)
+
+
+class TestRackGoogleAnalyticsInstanceMethods < Test::Unit::TestCase
+
+ include Rack::Test::Methods
+
+ class MockController < ActionController::Base
+ def index
+ set_ga_custom_var(1, "Items Removed", "Yes", GoogleAnalytics::CustomVar::SESSION_LEVEL)
+ track_ga_event("Users", "Login", "Standard")
+ render :inline => "<html><head><title>Title</title></head><body>Hello World</body></html>"
+ end
+ end
+
+ def controller
+ MockController.action(:index)
+ end
+
+ # Build an app to call our MockController with GoogleAnalytics middleware
+ def mock_app(options)
+ builder = Rack::Builder.new
+ builder.use Rack::GoogleAnalytics, options
+ builder.run controller
+ @app = builder.to_app
+ end
+
+ def app;
+ Rack::Lint.new(@app);
+ end
+
+ context "Instance Methods" do
+ setup { mock_app :async => true, :tracker => 'whatthe' }
+
+ context "pass variables to rack" do
+
+ should "have event tracking" do
+ get "/"
+ assert last_response.ok?
+
+ assert_match %r{\_gaq\.push}, last_response.body
+ assert_match %r{_trackEvent.*_trackPageview}m, last_response.body
+ assert_match %r{Users}, last_response.body
+ assert_match %r{Login}, last_response.body
+ assert_match %r{Standard}, last_response.body
+ end
+
+ should "have custom vars" do
+ get "/"
+ assert last_response.ok?
+
+ assert_match %r{\_gaq\.push}, last_response.body
+ assert_match %r{_setCustomVar.*_trackPageview}m, last_response.body
+ assert_match %r{Items Removed}, last_response.body
+ assert_match %r{Yes}, last_response.body
+ end
+ end
+ end
+
+end
View
4 test/test_rack-google-analytics.rb
@@ -10,7 +10,7 @@ class TestRackGoogleAnalytics < Test::Unit::TestCase
assert_match %r{\_gaq\.push}, last_response.body
assert_match %r{\'\_setAccount\', \"somebody\"}, last_response.body
assert_match %r{</script></head>}, last_response.body
- assert_equal "532", last_response.headers['Content-Length']
+ assert_equal "533", last_response.headers['Content-Length']
end
should "not add tracker to none html content-type" do
@@ -31,7 +31,7 @@ class TestRackGoogleAnalytics < Test::Unit::TestCase
should "add multiple domain script" do
get "/"
assert_match %r{'_setDomainName', \"mydomain.com\"}, last_response.body
- assert_equal "579", last_response.headers['Content-Length']
+ assert_equal "580", last_response.headers['Content-Length']
end
end
Something went wrong with that request. Please try again.