Skip to content

Commit

Permalink
initial commit; working on google analytics adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
Maik Vlcek committed Aug 24, 2011
1 parent fb29844 commit c62106c
Show file tree
Hide file tree
Showing 9 changed files with 494 additions and 7 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ pkg
#
# For MacOS:
#
#.DS_Store
.DS_Store

# RubyMine
.idea

# RVM
.rvmrc

# For TextMate
#*.tmproj
Expand Down
3 changes: 0 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
source "http://rubygems.org"
# Add dependencies required to use your gem here.
# Example:
# gem "activesupport", ">= 2.3.5"

# Add dependencies to develop your gem here.
# Include everything needed to run rake, tests, features, etc.
Expand Down
20 changes: 20 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
GEM
remote: http://rubygems.org/
specs:
git (1.2.5)
jeweler (1.6.4)
bundler (~> 1.0)
git (>= 1.2.5)
rake
rake (0.9.2)
rcov (0.9.10)
shoulda (2.11.3)

PLATFORMS
ruby

DEPENDENCIES
bundler (~> 1.0.0)
jeweler (~> 1.6.4)
rcov
shoulda
5 changes: 2 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ Jeweler::Tasks.new do |gem|
gem.name = "satellite"
gem.homepage = "http://github.com/mediavrog/satellite"
gem.license = "MIT"
gem.summary = %Q{TODO: one-line summary of your gem}
gem.description = %Q{TODO: longer description of your gem}
gem.summary = "Ruby on Rails server-side Analytics Tracker for Google and more"
gem.description = "Ruby on Rails server-side Analytics Tracker for Google and more"
gem.email = "maik.vlcek@mymobai.de"
gem.authors = ["Maik Vlcek"]
# dependencies defined in Gemfile
end
Jeweler::RubygemsDotOrgTasks.new

require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test'
Expand Down
56 changes: 56 additions & 0 deletions lib/satellite.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require 'open-uri'

# load string helpers
unless Object.const_defined?("ActiveSupport")
Dir.glob(File.dirname(__FILE__) + '/support/*') { |file| require file }
end

# load adapters
Dir.glob(File.dirname(__FILE__) + '/satellite/adapters/*') { |file| require file }

module Satellite

class NoAdapterError < NameError
end

class TrackerInterface
def initialize(adapter)
@adapter = adapter
end

def track_page_view(path=nil)
@adapter.track_page_view(path)
end

def track_event(category, action, label=nil, value=nil)
@adapter.track_event(category, action, label, value)
end

def set_custom_variable(slot, name, value, scope=nil)
@adapter.set_custom_variable(slot, name, value, scope)
end

def unset_custom_variable(slot)
@adapter.unset_custom_variable(slot)
end

def []=(key, value)
@adapter[key] = value
end

def [](key)
@adapter[key]
end
end

def self.get_tracker(type, params = { })
begin
tracker_klass = "Satellite::Adapters::#{type.to_s.camelcase}".constantize
rescue
raise NoAdapterError, "There is no such adapter like 'Satellite::Adapters::#{type.to_s.camelcase}'"
end

TrackerInterface.new(tracker_klass.new(params))
end

end
265 changes: 265 additions & 0 deletions lib/satellite/adapters/google_analytics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
module Satellite
module Adapters
class GoogleAnalytics

def initialize(params, use_ssl=false)
self.utm_params = extend_with_default_params(params)
self.utm_location = (use_ssl ? 'https://ssl' : 'http://www') + UTM_GIF_LOCATION
end

def track
puts utm_params.inspect

utm_url = utm_location + "?" + utm_params.to_query

puts "--------sending request to GA-----------------------"
puts utm_url
#open(utm_url)

# reset events / custom variables here
utm_params.delete(:utme)
end

def track_event(category, action, label=nil, value=nil)
utm_params[:utme].set_event(category, action, label, value)
track
end

def track_page_view(path=nil)
utm_params.merge({ :utmp => path }) if path
track
end

def set_custom_variable(index, name, value, scope=nil)
utm_params[:utme].set_custom_variable(index, name, value, scope)
end

def unset_custom_variable(index)
utm_params[:utme].unset_custom_variable(index)
end

def []=(key, value)
value = Utme.parse(value) if key.to_s == 'utme'
utm_params[key] = value
end

def [](key)
utm_params[key]
end

class << self
attr_accessor :account_id
end

protected

attr_accessor :utm_params, :utm_location

private

# seems to be the current version
# search for 'utmwv' in http://www.google-analytics.com/ga.js
VERSION = "5.1.5"
UTM_GIF_LOCATION = ".google-analytics.com/__utm.gif"

# adds default params
def extend_with_default_params(params)
{
:utme => Utme.parse(params[:utme]),
:utmwv => VERSION,
:utmn => rand(0x7fffffff).to_s,
:utmac => self.class.account_id
}.merge(params)
end

end
end
end

module Satellite
module Adapters
class GoogleAnalytics::Utme

def initialize
@custom_variables = CustomVariables.new
end

def set_event(category, action, label=nil, value=nil)
@event = Event.new(category, action, label, value)
self
end

def set_custom_variable(slot, name, value, scope=nil)
@custom_variables.set_custom_variable(slot, CustomVariable.new(name, value, scope))
self
end

def unset_custom_variable(slot)
@custom_variables.unset_custom_variable(slot)
self
end

def to_s
@event.to_s + @custom_variables.to_s
end

alias_method :to_param, :to_s

class << self

@@regex_event = /5\((\w+)\*(\w+)(\*(\w+))?\)(\((\d+)\))?/
@@regex_custom_variables = /8\(([^\)]*)\)9\(([^\)]*)\)(11\(([^\)]*)\))?/
@@regex_custom_variable_value = /((\d)!)?(\w+)/

def parse(args)
return self.new if args.nil?
case args
when String
return self.from_string(args)
when self
return args
else
raise ArgumentError, "Could parse argument neither as String nor GATracker::Utme"
end
end

def from_string(str)
utme = self.new

# parse event
str.gsub!(@@regex_event) do |match|
utme.set_event($1, $2, $4, $6)
''
end

# parse custom variables
str.gsub!(@@regex_custom_variables) do |match|
custom_vars = { }
match_names, match_values, match_scopes = $1, $2, $4

names = match_names.to_s.split('*')
values = match_values.to_s.split('*')
scopes = match_scopes.to_s.split('*')

raise ArgumentError, "Each custom variable must have a value defined." if names.length != values.length

names.each_with_index do |raw_name, i|
match_data = raw_name.match(@@regex_custom_variable_value)
slot, name = (match_data[2] || i+1).to_i, match_data[3]
custom_vars[slot] = { :name => name }
end

values.each_with_index do |raw_value, i|
match_data = raw_value.match(@@regex_custom_variable_value)
slot, value = (match_data[2] || i+1).to_i, match_data[3]
custom_vars[slot][:value] = value
end

scopes.each_with_index do |raw_scope, i|
match_data = raw_scope.match(@@regex_custom_variable_value)
slot, scope = (match_data[2] || i+1).to_i, match_data[3]
# silently ignore scope if there's no corresponding custom variable
custom_vars[slot][:scope] = scope if custom_vars[slot]
end

# finally set all the gathered custom vars
custom_vars.each do |key, custom_var|
utme.set_custom_variable(key, custom_var[:name], custom_var[:value], custom_var[:scope])
end
''
end

utme
end
end

private

Event = Struct.new(:category, :action, :opt_label, :opt_value) do
def to_s
output = "5(#{category}*#{action}"
output += "*#{opt_label}" if opt_label
output += ")"
output += "(#{opt_value})" if opt_value
output
end
end

CustomVariable = Struct.new(:name, :value, :opt_scope)

class CustomVariables

@@valid_keys = 1..5

def initialize
@contents = { }
end

def set_custom_variable(slot, custom_variable)
raise ArgumentError, "Cannot set a slot other than #{@@valid_keys}. Given #{slot}" if not @@valid_keys.include?(slot)
@contents[slot] = custom_variable
end

def unset_custom_variable(slot)
raise ArgumentError, "Cannot unset a slot other than #{@@valid_keys}. Given #{slot}" if not @@valid_keys.include?(slot)
@contents.delete(slot)
end

# follows google custom variable format
#
# 8(NAMES)9(VALUES)11(SCOPES)
#
# best explained by examples
#
# 1)
# pageTracker._setCustomVar(1,"foo", "val", 1)
# ==> 8(foo)9(bar)11(1)
#
# 2)
# pageTracker._setCustomVar(1,"foo", "val", 1)
# pageTracker._setCustomVar(2,"bar", "vok", 3)
# ==> 8(foo*bar)9(val*vok)11(1*3)
#
# 3)
# pageTracker._setCustomVar(1,"foo", "val", 1)
# pageTracker._setCustomVar(2,"bar", "vok", 3)
# pageTracker._setCustomVar(4,"baz", "vol", 1)
# ==> 8(foo*bar*4!baz)9(val*vak*4!vol)11(1*3*4!1)
#
# 4)
# pageTracker._setCustomVar(4,"foo", "bar", 1)
# ==> 8(4!foo)9(4!bar)11(4!1)
#
def to_s
return '' if @contents.empty?

ordered_keys = @contents.keys.sort
names = values = scopes = ''

ordered_keys.each do |slot|
custom_variable = @contents[slot]
predecessor = @contents[slot-1]

has_predecessor = !!predecessor
has_scoped_predecessor = !!predecessor.try(:opt_scope)

star = names.empty? ? '' : '*'
bang = (slot == 1 || has_predecessor) ? '' : "#{slot}!"

scope_star = scopes.empty? ? '' : '*'
scope_bang = (slot == 1 || has_scoped_predecessor) ? '' : "#{slot}!"

names += "#{star}#{bang}#{custom_variable.name}"
values += "#{star}#{bang}#{custom_variable.value}"
scopes += "#{scope_star}#{scope_bang}#{custom_variable.opt_scope}" if custom_variable.opt_scope
end

output = "8(#{names})9(#{values})"
output += "11(#{scopes})" if not scopes.empty?
output
end

end
end
end
end
Loading

0 comments on commit c62106c

Please sign in to comment.