event-tracker 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bc7102810ab7b2ebbedcdd8d6384f2388ed86e9d
4
+ data.tar.gz: a9e5c3a2c4a846b7d99e36a83918d10bc8c043b7
5
+ SHA512:
6
+ metadata.gz: bd06c36ce0da7b40fab4e7f1931e88717be67440eb391cc2104801414f7d92d0bf41c31c70c94fc7316571d024e0a4df398e96de57c5a8c2934ea99afef27748
7
+ data.tar.gz: 81d71fa242817d86beb2b528dd602110c4a4e6b9d6d61e187b9c82daa90cb89620edc1fb21d0c5ce21dff6a11972a780520ca69b2095293b6e0cb7fd6279c33b
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2014 Donald Piret.
2
+ Permission is hereby granted, free of charge, to any person obtaining
3
+ a copy of this software and associated documentation files (the
4
+ "Software"), to deal in the Software without restriction, including
5
+ without limitation the rights to use, copy, modify, merge, publish,
6
+ distribute, sublicense, and/or sell copies of the Software, and to
7
+ permit persons to whom the Software is furnished to do so, subject to
8
+ the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be
11
+ included in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
1
+ = Event Tracker
2
+
3
+ This library allows the easy implementation of server-side and client side tracking of various events.
4
+ It is currently designed to work with Segment.io (http://segment.io), but should be able to support other trackers as well.
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def merge_if(condition, target = nil)
3
+ condition ? self.merge(target ? target : condition) : self
4
+ end
5
+
6
+ def deep_merge_if(condition, target)
7
+ condition ? self.deep_merge(target) : self
8
+ end
9
+ end
@@ -0,0 +1,157 @@
1
+ require 'core_ext/hash'
2
+ require 'event_tracker/tracker'
3
+ require 'event_tracker/segment_io'
4
+
5
+ module EventTracker
6
+ class Config
7
+ attr_accessor :segment_io_key
8
+ attr_accessor :disabled
9
+ end
10
+
11
+ def self.config
12
+ @config ||= Config.new
13
+ end
14
+
15
+ def self.configure
16
+ yield self.config
17
+ end
18
+
19
+ def self.disabled?
20
+ @config.disabled == true
21
+ end
22
+
23
+ # Defines all the helper methods that become available at the controller level
24
+ module HelperMethods
25
+ def track_event(event_name, args = {})
26
+ (session[:tracker_events] ||= []) << [event_name, _sanitize_args(args)]
27
+ end
28
+
29
+ def register_properties(args)
30
+ (session[:tracker_properties] ||= {}).merge!(_sanitize_args(args))
31
+ end
32
+
33
+ def track_pageview(name, category, args = {})
34
+ (session[:tracker_pageviews] ||= []) << [name, category, _sanitize_args(args)]
35
+ end
36
+
37
+ def create_alias(identity1, identity2)
38
+ (session[:tracker_alias] ||= []) << [identity1, identity2]
39
+ end
40
+
41
+ def track_transaction(event_name, args = {})
42
+ (session[:tracker_transactions] ||= []) << [event_name, _sanitize_args(args)]
43
+ end
44
+
45
+ def identify_for_user(user, with_info = false)
46
+ _trackers.each do |tracker|
47
+ tracker.identify_for_identity(_tracker_identity(user), with_info)
48
+ end
49
+ end
50
+
51
+ def create_alias_for_user(identity1, identity2)
52
+ _trackers.each do |tracker|
53
+ tracker.create_alias_for_identity(identity1, identity2)
54
+ end
55
+ end
56
+
57
+ def track_event_for_user(user, event_name, args = {})
58
+ _trackers.each do |tracker|
59
+ tracker.track_event_for_identity(_tracker_identity(user), event_name, _sanitize_args(args))
60
+ end
61
+ end
62
+
63
+ def track_transaction_for_user(user, event_name, args = {})
64
+ _trackers.each do |tracker|
65
+ tracker.track_transaction_for_identity(_tracker_identity(user), event_name, _sanitize_args(args))
66
+ end
67
+ end
68
+ end
69
+
70
+ module ActionControllerExtension
71
+ def append_tracker
72
+ return if EventTracker.disabled? || _trackers.empty?
73
+ body = response.body
74
+ head_insert_at = body.index('</head')
75
+ return unless head_insert_at
76
+ body.insert head_insert_at, view_context.javascript_tag(_trackers.map {|t| t.init }.join("\n"))
77
+ body_insert_at = body.index('</body')
78
+ return unless body_insert_at
79
+ a = [] # Array of all javascript strings to insert
80
+
81
+ properties = session.delete(:tracker_properties)
82
+ events = session.delete(:tracker_events)
83
+ pageviews = session.delete(:tracker_pageviews)
84
+ alias_list = session.delete(:tracker_alias)
85
+ transactions = session.delete(:tracker_transactions)
86
+
87
+ _trackers.each do |tracker|
88
+ tracker.set_options(request: request)
89
+
90
+ a << tracker.identify(_tracker_identity)
91
+
92
+ # a << tracker.track_pageview() # No need for this anymore. Done by default
93
+ if pageviews.present?
94
+ pageviews.each do |url, properties|
95
+ a << tracker.track_pageview(url, properties)
96
+ end
97
+ end
98
+
99
+ a << tracker.add_properties(properties) if properties.present?
100
+
101
+ if events.present?
102
+ events.each do |event_name, properties|
103
+ a << tracker.track_event(event_name, properties)
104
+ end
105
+ end
106
+
107
+ if alias_list.present?
108
+ alias_list.each do |identity1, identity2|
109
+ a << tracker.create_alias(identity1, identity2)
110
+ end
111
+ end
112
+
113
+ if transactions.present?
114
+ transactions.each do |event_name, properties|
115
+ a << tracker.track_transaction(event_name, properties)
116
+ end
117
+ end
118
+ end
119
+
120
+ body.insert body_insert_at, view_context.javascript_tag(a.compact.join("\n"))
121
+ response.body = body
122
+ end
123
+
124
+ def _trackers
125
+ return [] if EventTracker.disabled?
126
+ @_trackers ||= begin
127
+ t = []
128
+ t << _segment_io_tracker if _segment_io_tracker
129
+ t
130
+ end
131
+ end
132
+
133
+ def _sanitize_args(args)
134
+ args.each do |k, v|
135
+ if v.is_a?(Hash)
136
+ args[k] = _sanitize_args(v)
137
+ elsif v.is_a?(String)
138
+ args[k] = ActionController::Base.helpers.sanitize(v)
139
+ else
140
+ args[k] = v
141
+ end
142
+ end
143
+ return args
144
+ end
145
+
146
+ def _tracker_identity(user = nil)
147
+ respond_to?(:analytics_identity, true) ? send(:analytics_identity, user) : nil
148
+ end
149
+
150
+ def _segment_io_tracker
151
+ @_segment_io_tracker ||= begin
152
+ key = EventTracker.config.segment_io_key
153
+ key ? EventTracker::SegmentIo.new(key: key) : nil
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,10 @@
1
+ module EventTracker
2
+ require 'rails'
3
+
4
+ class Railtie < Rails::Railtie
5
+
6
+ initializer 'event_tracker' do |app|
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,126 @@
1
+ module EventTracker
2
+ class SegmentIo < Tracker
3
+ JS_ESCAPE_MAP = {
4
+ '\\' => '\\\\',
5
+ '</' => '<\/',
6
+ "\r\n" => '\n',
7
+ "\n" => '\n',
8
+ "\r" => '\n',
9
+ '"' => '\\"',
10
+ "'" => "\\'"
11
+ }
12
+
13
+
14
+ def initialize(options = {})
15
+ @key = options[:key]
16
+ end
17
+
18
+ def init
19
+ <<-EOD
20
+ window.analytics=window.analytics||[],window.analytics.methods=["identify","group","track","page","pageview","alias","ready","on","once","off","trackLink","trackForm","trackClick","trackSubmit"],window.analytics.factory=function(t){return function(){var a=Array.prototype.slice.call(arguments);return a.unshift(t),window.analytics.push(a),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var key=window.analytics.methods[i];window.analytics[key]=window.analytics.factory(key)}window.analytics.load=function(t){if(!document.getElementById("analytics-js")){var a=document.createElement("script");a.type="text/javascript",a.id="analytics-js",a.async=!0,a.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.io/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n)}},window.analytics.SNIPPET_VERSION="2.0.9",
21
+ window.analytics.load("#{@key}");
22
+ window.analytics.page();
23
+ EOD
24
+ end
25
+
26
+ def identify(identity = nil)
27
+ if identity.present? && identity.has_key?(:id)
28
+ %Q{analytics.identify('#{identity[:id]}');}
29
+ elsif identity.present?
30
+ %Q{analytics.identify(#{ruby_hash_to_js(identity)});}
31
+ else
32
+ nil
33
+ end
34
+ end
35
+
36
+ def create_alias(identity1, identity2)
37
+ %Q{analytics.alias('#{identity1}', '#{identity2}');}
38
+ end
39
+
40
+ def add_properties(properties = nil)
41
+ %Q{analytics.track('set', #{ruby_hash_to_js(properties)});}
42
+ end
43
+
44
+ def track_pageview(name = nil, category = nil, properties = {}, options = {})
45
+ p = properties.empty? ? "" : ", #{ruby_hash_to_js(properties)}"
46
+ if category.present? && name.present?
47
+ %Q{analytics.page('#{category}', '#{name}'#{p});}
48
+ elsif name.present?
49
+ %Q{analytics.page('#{name}'#{p});}
50
+ else
51
+ %Q{analytics.page('');}
52
+ end
53
+ end
54
+
55
+ def track_event(event_name, properties)
56
+ p = properties.empty? ? "" : ", #{ruby_hash_to_js(properties.except(:analytics))}"
57
+ %Q{analytics.track('#{event_name}'#{p});}
58
+ end
59
+
60
+ def track_transaction(event_name, properties = {})
61
+ track_event(event_name, properties)
62
+ end
63
+
64
+ def identify_for_identity(identity, with_info = false)
65
+ return if EventTracker.disabled?
66
+ if identity.present? && identity.has_key?(:id)
67
+ client.identify({
68
+ user_id: "#{identity[:id]}",
69
+ }.merge_if(with_info, { traits: camelize_hash(identity.except(:id)) }))
70
+ end
71
+ end
72
+
73
+ def create_alias_for_identity(identity1, identity2)
74
+ return if EventTracker.disabled?
75
+ if identity1.present? && identity2.present?
76
+ client.alias(from: identity1, to: identity2)
77
+ end
78
+ end
79
+
80
+ def track_event_for_identity(identity, event_name, properties = {})
81
+ return if EventTracker.disabled?
82
+ if identity.present? && identity.has_key?(:id)
83
+ client.track(
84
+ user_id: "#{identity[:id]}",
85
+ event: "#{event_name}",
86
+ properties: camelize_hash(properties.except(:analytics))
87
+ )
88
+ end
89
+ end
90
+
91
+ def track_transaction_for_identity(identity, event_name, properties = {})
92
+ track_event_for_identity(identity, event_name, properties)
93
+ end
94
+
95
+ private
96
+
97
+ def camelize_hash(hash)
98
+ Hash[hash.map { |k, v| [k.to_s.camelize(:lower), v] }]
99
+ end
100
+
101
+ def ruby_hash_to_js(hash)
102
+ "{#{hash.collect{|key, val| "#{key.to_s.camelize(:lower)}: #{value_for_js(val)}" }.join(', ')}}"
103
+ end
104
+
105
+ def value_for_js(value)
106
+ case value
107
+ when Numeric, TrueClass, FalseClass
108
+ value
109
+ else
110
+ "'#{escape_javascript(value.try(:to_s))}'"
111
+ end
112
+ end
113
+
114
+ def escape_javascript(javascript)
115
+ if javascript
116
+ javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
117
+ else
118
+ ''
119
+ end
120
+ end
121
+
122
+ def client
123
+ AnalyticsRuby
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,52 @@
1
+ module EventTracker
2
+ # Abstract class from which every tracker inherits. Shows all supported methods
3
+ class Tracker
4
+ def init
5
+
6
+ end
7
+
8
+ def set_options(options = {})
9
+
10
+ end
11
+
12
+ def identify(identity = nil)
13
+
14
+ end
15
+
16
+ def create_alias(identity1, identity2)
17
+
18
+ end
19
+
20
+ def add_properties(properties = nil)
21
+
22
+ end
23
+
24
+ def track_pageview(url = nil, properties = {})
25
+
26
+ end
27
+
28
+ def track_event(event_name = nil, properties = {})
29
+
30
+ end
31
+
32
+ def track_transaction(event_name, properties = {})
33
+
34
+ end
35
+
36
+ def identify_for_identity(identity = nil, with_info = false)
37
+
38
+ end
39
+
40
+ def create_alias_for_identity(identity1, identity2)
41
+
42
+ end
43
+
44
+ def track_event_for_identity(identity, event_name = nil, properties = {})
45
+
46
+ end
47
+
48
+ def track_transaction_for_identity(identity, event_name, properties = {})
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module EventTracker
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventsController, type: :controller do
4
+ render_views
5
+
6
+ describe '#index' do
7
+ it 'includes the segment.io javascript in the user browser' do
8
+ get :index
9
+ expect(response.body).to include('http://")+"cdn.segment.io/analytics.js/v1/"+t+"/analytics.min.js"') # Invluding the JS
10
+ end
11
+
12
+ it 'identifies the user for segment.io' do
13
+ get :index
14
+ expect(response.body).to include('analytics.identify(\'dummyId\');') # Identifying the user
15
+ end
16
+
17
+ it 'tracks the event for segment.io' do
18
+ get :index
19
+ expect(response.body).to include('analytics.track(\'List events\');') # Tracking the event
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventTracker do
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: event-tracker
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Donald Piret
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: analytics-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ - - "<="
35
+ - !ruby/object:Gem::Version
36
+ version: '5.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ - - "<="
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ - - "<="
55
+ - !ruby/object:Gem::Version
56
+ version: '5.0'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '3.0'
64
+ - - "<="
65
+ - !ruby/object:Gem::Version
66
+ version: '5.0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: bundler
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.3'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.3'
81
+ - !ruby/object:Gem::Dependency
82
+ name: rake
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '10.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '10.0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: rspec-rails
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3.0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '3.0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: sqlite3
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.3'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.3'
123
+ description: This library allows the easy implementation of server-side and client
124
+ side tracking of various events.
125
+ email: donald@donaldpiret.com
126
+ executables: []
127
+ extensions: []
128
+ extra_rdoc_files: []
129
+ files:
130
+ - MIT-LICENSE
131
+ - README.rdoc
132
+ - lib/core_ext/hash.rb
133
+ - lib/event_tracker.rb
134
+ - lib/event_tracker/railtie.rb
135
+ - lib/event_tracker/segment_io.rb
136
+ - lib/event_tracker/tracker.rb
137
+ - lib/event_tracker/version.rb
138
+ - spec/controllers/events_controller_spec.rb
139
+ - spec/event_tracker_spec.rb
140
+ homepage: https://github.com/donaldpiret/event-tracker
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.9.3
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.2.2
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Quick and easy tracking of server-side and client side events for Rails
164
+ test_files:
165
+ - spec/controllers/events_controller_spec.rb
166
+ - spec/event_tracker_spec.rb