sojourn 0.1.1 → 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f2ae69a242be094ed09f87d8721b0b12c7369a4
4
- data.tar.gz: 16deb25d1a1598c08cd96162a59ec25a344fde88
3
+ metadata.gz: 9caf4fdd9129f3391d00292849ea95096e2e7bb8
4
+ data.tar.gz: 3cae47fe7eea5430c668cb8d63993ab9eca5a89f
5
5
  SHA512:
6
- metadata.gz: 99a18bf20addc6f178c68b9fc96a990c512ef6c1839c54755dc523327c77984dafedff591a122da996e536e030de6e247800f3643f00b9425c937c22770d7788
7
- data.tar.gz: dc459a98a2a177b53aacebfd400b37853ad61c6da727ed48713884d1f7960c90146e7c362211391a551472aed00389038204920da5724ce5f9f2cc7d0ad7618b
6
+ metadata.gz: cb52529bf9c8f7623ad5e35ace62dc89933c7c11b086a39241d92b98bfaebf4fc1d7de7f4b62f009f107fce7e8cad8d8c1548bbbdc31b61b6b5b8d1a581b6567
7
+ data.tar.gz: 6db0dd0268c3fd39d40340b6e9254b436f367f03bfb013259c80920e445b239adc4e2db329b76cd11e362153d1ef78de43b3545ee472a781170b53dce192ade3
data/.gitignore CHANGED
@@ -12,4 +12,3 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
- .DS_Store
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Sojourn [![Build Status](https://img.shields.io/travis/smudge/sojourn.svg)](https://travis-ci.org/smudge/sojourn) [![Code Climate](https://img.shields.io/codeclimate/github/smudge/sojourn.svg)](https://codeclimate.com/github/smudge/sojourn) [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/smudge/sojourn.svg)](https://codeclimate.com/github/smudge/sojourn/coverage)
1
+ # Sojourn
2
2
 
3
3
  Simple source & event tracking for Rails. This gem automatically tracks *sojourners*
4
4
  (i.e. unique visitors) based on:
@@ -11,22 +11,18 @@ Simple source & event tracking for Rails. This gem automatically tracks *sojourn
11
11
 
12
12
  ## How It Works
13
13
 
14
- Whenever a new visitor ("sojourner") arrives to the site, an event is tracked containing
15
- basic data about their browser and where they came from. Similar events are also tracked
16
- whenever a user logs in, logs out, or visits again from an external site. In addition,
17
- you can track a custom event anytime a visitor does something of interest to you.
14
+ **Everything is tracked in the form of events.** Yep, events all the way down.
15
+ (See 'Why Events?' below for the reasoning behind this.)
18
16
 
19
- Ultimately, rather than storing parts of the data in separate tables, **all data is
20
- tracked in the form of events.** Yep, events all the way down. (See 'Why Events?' below
21
- for the reasoning behind this.)
17
+ Sojourn assigns each *sojourner* a UUID, which is tracked across requests. All events are
18
+ associated with this UUID and with the current user's ID (if logged-in).
22
19
 
23
- Sojourn assigns each "sojourner" a UUID, which is tracked across requests. All events are
24
- associated with this UUID and with the current user's ID (if logged-in). The current
25
- request is also assigned a UUID (which defaults to the `X-Request-ID` header).
20
+ Events (`Sojourn::Event`) consist of a name, a set of properties (key-value hash) and information
21
+ about the request. In the current ActiveRecord implementation, requests (`Sojourn::Request`) can
22
+ be queried separately and may have many events. Requests also track browser (`Sojourn::Browser`)
23
+ and campaign (`Sojourn::Campaign`) info as unique models. See 'Usage' below for the details
24
+ of these models.
26
25
 
27
- Events consist of an event name (defining a collection of events), a session UUID,
28
- and a set of properties (key-value data) which includes information about the request.
29
- In the PostgreSQL implementation, we use a `JSONB` column to store the key-value data.
30
26
 
31
27
  ## Usage
32
28
 
@@ -34,16 +30,37 @@ In the PostgreSQL implementation, we use a `JSONB` column to store the key-value
34
30
  # Track a custom event (highly encouraged!):
35
31
  sojourn.track! 'clicked call-to-action', plan_choice: 'enterprise'
36
32
 
37
- # Read events using ActiveRecord
38
33
  e = Sojourn::Event.last
39
34
  e.name # event name (e.g. 'clicked call-to-action')
40
35
  e.sojourner_uuid # uuid tracked across requests, stored in cookie
41
36
  e.user # User or nil
42
37
  e.properties # key-value hash (e.g. "{ plan_choice: 'enterprise' }")
38
+ e.request # Sojourn::Request object
39
+
40
+ r = Sojourn::Request.last
41
+ r.referer
42
+ r.host
43
+ r.path
44
+ r.controller
45
+ r.action
46
+ r.params
47
+ r.method
48
+ r.ip_address
49
+ r.campaign # Sojourn::Campaign object (nil if no campaign detected)
50
+ r.browser # Sojourn::Browser object
51
+
52
+ b = Sojourn::Browser.last
53
+ b.known? # whether or not the browser was detected successfully
54
+ b.user_agent
55
+ b.name
56
+ b.version
57
+ b.platform
58
+ b.bot?
59
+
60
+ c = Sojourn::Campaign.last
61
+ c.path # Base path (e.g. '/posts/2')
62
+ c.params # Notable (tracked) params, sorted. (Typically utm-style, but configurable.)
43
63
 
44
- # If you don't have access to a controller context (i.e. the event is not occurring during a web
45
- # request), you can still track a raw event like this:
46
- Sojourn.track_raw_event! 'subscription expired', plan: 'enterprise', customer_id: 'xyb123'
47
64
  ```
48
65
 
49
66
  ## Default Events
@@ -56,7 +73,7 @@ The three built-in events are as follows:
56
73
  '!logged_out' # The sojourner has logged-out.
57
74
  ```
58
75
 
59
- A `'!sojourning'` event takes place whenever any of the following conditions is met:
76
+ A `'!sojourning'` event takes place whenever any of the following is true:
60
77
 
61
78
  * The sojourner has never been seen before (i.e. direct traffic of some kind)
62
79
  * The referer is from an external source (i.e. not the current `request.host`)
@@ -64,42 +81,6 @@ A `'!sojourning'` event takes place whenever any of the following conditions is
64
81
  initializer.)
65
82
 
66
83
 
67
- ## Properties
68
-
69
- In addition to properties that you manually add, events will automatically include data about
70
- the current web request. An example looks like this:
71
-
72
- ```json
73
- {
74
- "custom_property":"value",
75
- "request":{
76
- "uuid":"5e698f6ca74a016c49ca6b91a79cada7",
77
- "host":"example.com",
78
- "path":"/my-news",
79
- "controller":"news",
80
- "action":"index",
81
- "method":"get",
82
- "params":{
83
- "utm_campaign":"daily_updates",
84
- "page":"1"
85
- },
86
- "referer":"https://mail.google.com",
87
- "ip_address":"42.42.42.42",
88
- "user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.48 Safari/537.36"
89
- },
90
- "browser":{
91
- "bot":false,
92
- "name":"Chrome",
93
- "known":true,
94
- "version":"48",
95
- "platform":"mac"
96
- },
97
- "campaign":{
98
- "utm_campaign":"daily_updates"
99
- }
100
- }
101
- ```
102
-
103
84
  ## Installation
104
85
 
105
86
  Add this line to your application's Gemfile:
@@ -121,17 +102,10 @@ To install migrations and the `sojourn.rb` initializer, execute:
121
102
  The idea is that, at a certain scale, this kind of tracking should be dumped directly into
122
103
  append-only logs (or an event bus / messaging queue) for asynchronous processing.
123
104
 
124
- This is made easier when everything can be represented, at a basic level, as a set of discrete
125
- events. In theory, it works with just about any data store, and makes for easy time series and
126
- funnel analysis. I'd like to move away from ActiveRecord at some point and open up the door for
127
- other, more horizontally scalable data backends, ideally with a focus on streaming data (e.g.
128
- Kafka combined with Samza or Storm).
129
-
130
- An added benfit of storing the start of each visit as its own event in the series (i.e. the
131
- built-in `!sojourning` event) is that you can change the length of your visit window after
132
- the fact and re-run your analysis. The more traditional approach is to tag each event with
133
- some kind of incrementing visit ID, which forces you into defining what a "unique visit"
134
- means for your product before you've even collected any data.
105
+ This is made easier when everything can be represented, at a basic level, as discrete events.
106
+ In theory, it works with just about any data store, and makes for easy time series and funnel
107
+ analysis. I'd like to move away from ActiveRecord at some point and open up the door for other,
108
+ highly scalable data backends.
135
109
 
136
110
  ## Current Limitations (i.e. the 'todo' list)
137
111
 
@@ -141,3 +115,4 @@ means for your product before you've even collected any data.
141
115
  website.
142
116
  * Relies solely on cookies to track visitor UUID across requests (no JS, fingerprinting, etc)
143
117
  * Relies on ActiveRecord for storage. (At a bigger scale, append-only logs are preferred)
118
+ * There are no tests.
data/Rakefile CHANGED
@@ -1,9 +1 @@
1
1
  require 'bundler/gem_tasks'
2
-
3
- begin
4
- require 'rspec/core/rake_task'
5
- RSpec::Core::RakeTask.new(:spec)
6
- task default: :spec
7
- rescue LoadError
8
- puts 'rspec rake tasks not found'
9
- end
@@ -21,7 +21,7 @@ module Sojourn
21
21
  end
22
22
 
23
23
  def create_migrations
24
- %w(events).map { |m| "create_sojourn_#{m}" }.each do |name|
24
+ %w(campaigns browsers requests events).map { |m| "create_sojourn_#{m}" }.each do |name|
25
25
  if self.class.migration_exists?('db/migrate', name)
26
26
  say " #{set_color('skip', :yellow)} #{name}.rb (migration already exists)"
27
27
  else
@@ -0,0 +1,13 @@
1
+ class CreateSojournBrowsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :sojourn_browsers do |t|
4
+ t.text :user_agent, unique: true
5
+ t.string :name
6
+ t.string :version
7
+ t.string :platform
8
+ t.boolean :known
9
+ t.boolean :bot
10
+ end
11
+ add_index :sojourn_browsers, [:user_agent]
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ class CreateSojournCampaigns < ActiveRecord::Migration
2
+ def change
3
+ create_table :sojourn_campaigns do |t|
4
+ t.string :params, limit: 2048
5
+ t.timestamp :created_at
6
+ end
7
+ add_index :sojourn_campaigns, :params
8
+ end
9
+ end
@@ -4,10 +4,12 @@ class CreateSojournEvents < ActiveRecord::Migration
4
4
  t.string :sojourner_uuid, limit: 36, null: false
5
5
  t.string :name
6
6
  t.column :properties, :jsonb
7
+ t.references :sojourn_request
7
8
  t.references :user
8
9
  t.timestamp :created_at
9
10
  end
10
11
  add_index :sojourn_events, [:sojourner_uuid]
12
+ add_index :sojourn_events, [:sojourn_request_id]
11
13
  add_index :sojourn_events, [:user_id]
12
14
  add_index :sojourn_events, [:created_at]
13
15
  add_index :sojourn_events, [:name]
@@ -0,0 +1,19 @@
1
+ class CreateSojournRequests < ActiveRecord::Migration
2
+ def change
3
+ create_table :sojourn_requests do |t|
4
+ t.references :sojourn_campaign
5
+ t.references :sojourn_browser
6
+ t.string :host, limit: 2048
7
+ t.string :path, limit: 2048
8
+ t.string :method
9
+ t.string :controller
10
+ t.string :action
11
+ t.string :ip_address
12
+ t.text :params
13
+ t.text :referer
14
+ t.timestamp :created_at
15
+ end
16
+ add_index :sojourn_requests, [:sojourn_campaign_id]
17
+ add_index :sojourn_requests, [:sojourn_browser_id]
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ require 'browser'
2
+
3
+ module Sojourn
4
+ class Browser < ActiveRecord::Base
5
+ has_many :requests, foreign_key: :sojourn_browser_id
6
+ has_many :events, through: :requests
7
+
8
+ def self.from_request(request)
9
+ where(user_agent: request.user_agent).first_or_initialize
10
+ end
11
+
12
+ before_validation do
13
+ self.name ||= browser.name
14
+ self.version ||= browser.version
15
+ self.platform ||= browser.platform
16
+ self.known ||= browser.known?
17
+ self.bot ||= browser.bot?
18
+ true # otherwise .valid? will return false
19
+ end
20
+
21
+ private
22
+
23
+ def browser
24
+ @browser ||= ::Browser.new(user_agent: user_agent)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module Sojourn
2
+ class Campaign < ActiveRecord::Base
3
+
4
+ has_many :requests, foreign_key: :sojourn_campaign_id
5
+ has_many :events, through: :requests
6
+
7
+ class << self
8
+
9
+ def from_request(request)
10
+ return unless (request.tracked_params).any?
11
+ where(params: request.tracked_params.to_param.try(:truncate, 2048)).first_or_initialize
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -1,6 +1,7 @@
1
1
  require_relative 'session_stores/cookie'
2
2
 
3
3
  class Configuration
4
+
4
5
  attr_accessor :campaign_params
5
6
  attr_accessor :session_store
6
7
  attr_accessor :cookie_name
@@ -23,4 +24,5 @@ private
23
24
  self.cookie_name = :_sojourn
24
25
  self.tracking_enabled = true
25
26
  end
27
+
26
28
  end
@@ -2,6 +2,7 @@ require_relative 'tracker'
2
2
 
3
3
  module Sojourn
4
4
  module Controller
5
+
5
6
  def self.included(base)
6
7
  base.before_filter :track_sojourning
7
8
  base.before_filter :save_sojourn_session
@@ -20,5 +21,6 @@ module Sojourn
20
21
  def save_sojourn_session
21
22
  sojourn.update_session!
22
23
  end
24
+
23
25
  end
24
26
  end
data/lib/sojourn/event.rb CHANGED
@@ -2,9 +2,12 @@ require_relative 'serializers/indifferent_json'
2
2
 
3
3
  module Sojourn
4
4
  class Event < ActiveRecord::Base
5
- DEFAULT_FIELDS = %i(id sojourner_uuid name properties user_id created_at)
5
+ DEFAULT_FIELDS = [:id, :sojourner_uuid, :name, :properties, :sojourn_request_id, :user_id, :created_at]
6
6
 
7
+ belongs_to :request, foreign_key: :sojourn_request_id
7
8
  belongs_to :user
9
+ has_one :campaign, through: :request
10
+ has_one :browser, through: :request
8
11
 
9
12
  serialize :properties, Serializers::IndifferentJSON
10
13
 
@@ -1,20 +1,38 @@
1
- require 'securerandom'
1
+ require_relative 'campaign'
2
+ require_relative 'browser'
3
+ require_relative 'serializers/symbol'
2
4
  require 'addressable/uri'
3
- require 'browser'
4
- require 'referer-parser'
5
5
 
6
6
  module Sojourn
7
- class Request
8
- KEYS = %w(uuid referer host path controller action params method ip_address user_agent)
7
+ class Request < ActiveRecord::Base
8
+ attr_accessor :user_agent
9
9
 
10
- attr_reader :request
10
+ serialize :method, Serializers::Symbol
11
+ serialize :params
11
12
 
12
- def initialize(request)
13
- @request = request
13
+ belongs_to :campaign, foreign_key: :sojourn_campaign_id
14
+ belongs_to :browser, foreign_key: :sojourn_browser_id
15
+ has_many :events, foreign_key: :sojourn_request_id
16
+
17
+ def self.from_request(request)
18
+ new referer: request.referer.try(:truncate, 2048),
19
+ host: request.host.try(:truncate, 2048),
20
+ path: request.path.try(:truncate, 2048),
21
+ controller: request.params[:controller],
22
+ action: request.params[:action],
23
+ params: request.filtered_parameters.with_indifferent_access.except(:controller, :action),
24
+ method: request.request_method_symbol,
25
+ ip_address: request.remote_ip,
26
+ user_agent: request.user_agent
27
+ end
28
+
29
+ before_validation do
30
+ self.campaign ||= Campaign.from_request(self)
31
+ self.browser ||= Browser.from_request(self) if user_agent
14
32
  end
15
33
 
16
34
  def outside_referer?
17
- referer.present? && referer_host != host
35
+ referer.present? && Addressable::URI.parse(referer).host != host
18
36
  end
19
37
 
20
38
  def any_utm_data?
@@ -25,90 +43,20 @@ module Sojourn
25
43
  Hash[downcased_params.slice(*tracked_param_keys).delete_if { |_, v| v.blank? }.sort]
26
44
  end
27
45
 
28
- def raw_data
29
- Hash[KEYS.map { |k| [k, send(k)] }].with_indifferent_access
30
- end
31
-
32
46
  def browser_data
33
47
  return @browser_data if @browser_data
34
- @browser_data = { known: false } unless browser
35
- @browser_data ||= {
36
- name: browser.name,
37
- version: browser.version,
38
- platform: browser.platform,
39
- bot: browser.bot?,
40
- known: browser.known?
48
+ b = browser.try(:send, :browser) || ::Browser.new(user_agent: user_agent)
49
+ @browser_data = {
50
+ name: b.name,
51
+ version: b.version,
52
+ platform: b.platform,
53
+ bot: b.bot?,
54
+ known: b.known?
41
55
  }
42
56
  end
43
57
 
44
- def referer_data
45
- return @referer_data if @referer_data
46
- p = RefererParser::Parser.new.parse(sanitized_referer)
47
- @referer_data = {
48
- known: p[:known],
49
- host: referer_host,
50
- source: p[:source],
51
- medium: p[:medium],
52
- term: p[:term]
53
- }
54
- rescue
55
- @referer_data = {}
56
- end
57
-
58
58
  private
59
59
 
60
- def uuid
61
- @uuid ||= request.uuid || SecureRandom.uuid
62
- end
63
-
64
- def referer
65
- @referer ||= request.referer.try(:truncate, 2048)
66
- end
67
-
68
- def host
69
- @host ||= request.host.try(:truncate, 2048)
70
- end
71
-
72
- def path
73
- @path ||= request.path.try(:truncate, 2048)
74
- end
75
-
76
- def controller
77
- @controller ||= request.params[:controller]
78
- end
79
-
80
- def action
81
- @action ||= request.params[:action]
82
- end
83
-
84
- def params
85
- @params ||= request.filtered_parameters.with_indifferent_access.except(:controller, :action)
86
- end
87
-
88
- def method
89
- @method ||= request.request_method_symbol
90
- end
91
-
92
- def ip_address
93
- @ip_address ||= request.remote_ip
94
- end
95
-
96
- def user_agent
97
- @user_agent ||= request.user_agent
98
- end
99
-
100
- def referer_host
101
- @referer_host ||= parsed_referer.host
102
- end
103
-
104
- def sanitized_referer
105
- @sanitized_referer ||= parsed_referer.display_uri
106
- end
107
-
108
- def parsed_referer
109
- @parsed_referer ||= Addressable::URI.parse(referer)
110
- end
111
-
112
60
  def downcased_params
113
61
  params.each_with_object({}) { |(k, v), h| h[k.to_s.downcase] = v }
114
62
  end
@@ -116,9 +64,5 @@ module Sojourn
116
64
  def tracked_param_keys
117
65
  Sojourn.config.campaign_params.map(&:to_s).map(&:downcase)
118
66
  end
119
-
120
- def browser
121
- @browser ||= Browser.new(user_agent: user_agent) if user_agent
122
- end
123
67
  end
124
68
  end
@@ -0,0 +1,13 @@
1
+ module Sojourn
2
+ module Serializers
3
+ class Symbol
4
+ def self.load(string)
5
+ string.to_sym
6
+ end
7
+
8
+ def self.dump(symbol)
9
+ symbol.to_s
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,6 +1,7 @@
1
1
  module Sojourn
2
2
  module SessionStores
3
3
  class Cookie
4
+
4
5
  def initialize(ctx)
5
6
  @ctx = ctx
6
7
  end
@@ -42,6 +43,7 @@ module Sojourn
42
43
  def cookie_name
43
44
  @cookie_name ||= Sojourn.config.cookie_name
44
45
  end
46
+
45
47
  end
46
48
  end
47
49
  end
@@ -13,7 +13,8 @@ module Sojourn
13
13
  def track!(event_name, properties = {}, user_id = current_user_id)
14
14
  return unless Sojourn.tables_exist?
15
15
  properties = default_event_properties.merge(properties)
16
- Event.create! sojourner_uuid: sojourner_uuid, name: event_name,
16
+ Event.reset_column_information if Event.columns_hash['properties'].type != :jsonb
17
+ Event.create! sojourner_uuid: sojourner_uuid, name: event_name, request: request,
17
18
  properties: properties, user_id: user_id
18
19
  end
19
20
 
@@ -37,7 +38,7 @@ module Sojourn
37
38
  private
38
39
 
39
40
  def request
40
- @request ||= Request.new(ctx.request)
41
+ @request ||= Request.from_request(ctx.request)
41
42
  end
42
43
 
43
44
  def session
@@ -70,10 +71,8 @@ module Sojourn
70
71
  Sojourn.config.default_properties_block
71
72
  end
72
73
  @ctx.sojourn_event_properties(properties) if @ctx.respond_to? :sojourn_event_properties
73
- properties.merge! request: request.raw_data
74
- properties.merge! campaign: request.tracked_params if request.tracked_params.any?
74
+ properties.merge!(campaign: request.tracked_params) if request.tracked_params.any?
75
75
  properties.merge! browser: request.browser_data
76
- properties.merge! referer: request.referer_data if request.referer_data.any?
77
76
  properties
78
77
  end
79
78
  end
@@ -1,3 +1,3 @@
1
1
  module Sojourn
2
- VERSION = '0.1.1'
2
+ VERSION = '1.0.0.pre'
3
3
  end
data/lib/sojourn.rb CHANGED
@@ -16,7 +16,7 @@ module Sojourn
16
16
  end
17
17
 
18
18
  def self.tables_exist?
19
- @tables_exist ||= %w(sojourn_events sojourn_requests)
19
+ @tables_exist ||= %w(sojourn_events sojourn_requests sojourn_browsers sojourn_campaigns)
20
20
  .map { |t| ActiveRecord::Base.connection.table_exists?(t) }.all?
21
21
  end
22
22
 
data/sojourn.gemspec CHANGED
@@ -22,13 +22,7 @@ sojourn tracks the referer, utm data, and logged-in user (if any)).gsub("\n", '
22
22
 
23
23
  spec.add_development_dependency 'bundler', '~> 1.7'
24
24
  spec.add_development_dependency 'rake', '~> 10.0'
25
- spec.add_development_dependency 'rspec', '~> 3.4.0'
26
- spec.add_development_dependency 'rspec-its', '~> 1.2.0'
27
- spec.add_development_dependency 'sqlite3', '~> 1.3.0'
28
- spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4.8'
29
25
 
30
26
  spec.add_dependency 'browser', '>= 0.8.0'
31
27
  spec.add_dependency 'addressable', '>= 2.3.1'
32
- spec.add_dependency 'referer-parser', '~> 0.3.0'
33
- spec.add_dependency 'rails', '~> 3.2.0'
34
28
  end
metadata CHANGED
@@ -1,190 +1,103 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sojourn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Smudge
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-07 00:00:00.000000000 Z
11
+ date: 2015-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.7'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 3.4.0
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 3.4.0
55
- - !ruby/object:Gem::Dependency
56
- name: rspec-its
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 1.2.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 1.2.0
69
- - !ruby/object:Gem::Dependency
70
- name: sqlite3
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 1.3.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 1.3.0
83
- - !ruby/object:Gem::Dependency
84
- name: codeclimate-test-reporter
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: 0.4.8
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: 0.4.8
97
41
  - !ruby/object:Gem::Dependency
98
42
  name: browser
99
43
  requirement: !ruby/object:Gem::Requirement
100
44
  requirements:
101
- - - ">="
45
+ - - '>='
102
46
  - !ruby/object:Gem::Version
103
47
  version: 0.8.0
104
48
  type: :runtime
105
49
  prerelease: false
106
50
  version_requirements: !ruby/object:Gem::Requirement
107
51
  requirements:
108
- - - ">="
52
+ - - '>='
109
53
  - !ruby/object:Gem::Version
110
54
  version: 0.8.0
111
55
  - !ruby/object:Gem::Dependency
112
56
  name: addressable
113
57
  requirement: !ruby/object:Gem::Requirement
114
58
  requirements:
115
- - - ">="
59
+ - - '>='
116
60
  - !ruby/object:Gem::Version
117
61
  version: 2.3.1
118
62
  type: :runtime
119
63
  prerelease: false
120
64
  version_requirements: !ruby/object:Gem::Requirement
121
65
  requirements:
122
- - - ">="
66
+ - - '>='
123
67
  - !ruby/object:Gem::Version
124
68
  version: 2.3.1
125
- - !ruby/object:Gem::Dependency
126
- name: referer-parser
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: 0.3.0
132
- type: :runtime
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: 0.3.0
139
- - !ruby/object:Gem::Dependency
140
- name: rails
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: 3.2.0
146
- type: :runtime
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: 3.2.0
153
- description: " Sojourn tracks site visitors and sources, with the ability to recognise
69
+ description: ' Sojourn tracks site visitors and sources, with the ability to recognise
154
70
  multiple sources per visitor. Each time a new source is detected, sojourn tracks
155
- the referer, utm data, and logged-in user (if any)"
71
+ the referer, utm data, and logged-in user (if any)'
156
72
  email:
157
73
  - nathan@ngriffith.com
158
74
  executables: []
159
75
  extensions: []
160
76
  extra_rdoc_files: []
161
77
  files:
162
- - ".codeclimate.yml"
163
- - ".gitignore"
164
- - ".rubocop.yml"
165
- - ".travis.yml"
78
+ - .gitignore
166
79
  - Gemfile
167
80
  - README.md
168
81
  - Rakefile
169
82
  - lib/generators/sojourn/install_generator.rb
170
83
  - lib/generators/sojourn/templates/config_initializer.rb
84
+ - lib/generators/sojourn/templates/create_sojourn_browsers.rb
85
+ - lib/generators/sojourn/templates/create_sojourn_campaigns.rb
171
86
  - lib/generators/sojourn/templates/create_sojourn_events.rb
87
+ - lib/generators/sojourn/templates/create_sojourn_requests.rb
172
88
  - lib/sojourn.rb
89
+ - lib/sojourn/browser.rb
90
+ - lib/sojourn/campaign.rb
173
91
  - lib/sojourn/configuration.rb
174
92
  - lib/sojourn/controller.rb
175
93
  - lib/sojourn/event.rb
176
94
  - lib/sojourn/request.rb
177
95
  - lib/sojourn/serializers/indifferent_json.rb
96
+ - lib/sojourn/serializers/symbol.rb
178
97
  - lib/sojourn/session_stores/cookie.rb
179
98
  - lib/sojourn/tracker.rb
180
99
  - lib/sojourn/version.rb
181
100
  - sojourn.gemspec
182
- - spec/mocks/controller.rb
183
- - spec/mocks/cookie.rb
184
- - spec/mocks/request.rb
185
- - spec/mocks/user.rb
186
- - spec/spec_helper.rb
187
- - spec/tracker_spec.rb
188
101
  homepage: ''
189
102
  licenses:
190
103
  - ''
@@ -195,24 +108,18 @@ require_paths:
195
108
  - lib
196
109
  required_ruby_version: !ruby/object:Gem::Requirement
197
110
  requirements:
198
- - - ">="
111
+ - - '>='
199
112
  - !ruby/object:Gem::Version
200
113
  version: '0'
201
114
  required_rubygems_version: !ruby/object:Gem::Requirement
202
115
  requirements:
203
- - - ">="
116
+ - - '>'
204
117
  - !ruby/object:Gem::Version
205
- version: '0'
118
+ version: 1.3.1
206
119
  requirements: []
207
120
  rubyforge_project:
208
- rubygems_version: 2.4.5.1
121
+ rubygems_version: 2.4.1
209
122
  signing_key:
210
123
  specification_version: 4
211
124
  summary: Simple user source tracking for Rails.
212
- test_files:
213
- - spec/mocks/controller.rb
214
- - spec/mocks/cookie.rb
215
- - spec/mocks/request.rb
216
- - spec/mocks/user.rb
217
- - spec/spec_helper.rb
218
- - spec/tracker_spec.rb
125
+ test_files: []
data/.codeclimate.yml DELETED
@@ -1,11 +0,0 @@
1
- ---
2
- engines:
3
- fixme:
4
- enabled: true
5
- rubocop:
6
- enabled: true
7
- ratings:
8
- paths:
9
- - "**.rb"
10
- exclude_paths:
11
- - spec/**/*
data/.rubocop.yml DELETED
@@ -1,6 +0,0 @@
1
- Metrics/LineLength:
2
- Max: 99
3
- Style/AccessModifierIndentation:
4
- EnforcedStyle: outdent
5
- Style/Documentation:
6
- Enabled: false
data/.travis.yml DELETED
@@ -1,8 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.4
4
- - 2.1.8
5
- - 2.0.0-p648
6
- addons:
7
- code_climate:
8
- repo_token: f8a0ab3c3b30ca6a3091f30a9e344344a80f919837de817d576c0e3f174a4e2c
@@ -1,15 +0,0 @@
1
- require 'mocks/user'
2
- require 'mocks/request'
3
- require 'mocks/cookie'
4
-
5
- module Mocks
6
- class Controller
7
- attr_accessor :current_user, :request, :cookies
8
-
9
- def initialize(user = User.new, request = Request.new, cookies = Cookie.new)
10
- self.current_user = user
11
- self.request = request
12
- self.cookies = cookies
13
- end
14
- end
15
- end
data/spec/mocks/cookie.rb DELETED
@@ -1,30 +0,0 @@
1
- # Credit: https://kylecrum.wordpress.com/2009/07/19/mock-cookies-in-rails-tests-the-easy-way/
2
-
3
- module Mocks
4
- class Cookie < Hash
5
- def [](name)
6
- super(name)
7
- end
8
-
9
- def []=(key, options)
10
- if options.is_a?(Hash)
11
- options.symbolize_keys!
12
- else
13
- options = { value: options }
14
- end
15
- super(key, options[:value])
16
- end
17
-
18
- def delete(key, _)
19
- super(key)
20
- end
21
-
22
- def permanent
23
- self
24
- end
25
-
26
- def signed
27
- self
28
- end
29
- end
30
- end
@@ -1,56 +0,0 @@
1
- require 'securerandom'
2
-
3
- module Mocks
4
- class Request
5
- CHROME_UA = <<-EOF.squish
6
- Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
7
- Chrome/41.0.2228.0 Safari/537.36
8
- EOF
9
-
10
- attr_reader :opts
11
-
12
- def initialize(opts = {})
13
- @opts = opts
14
- end
15
-
16
- def uuid
17
- opts[:uuid] || SecureRandom.uuid
18
- end
19
-
20
- def referer
21
- opts[:referer]
22
- end
23
-
24
- def host
25
- opts[:host] || 'http://example.com'
26
- end
27
-
28
- def path
29
- opts[:path] || '/'
30
- end
31
-
32
- def params
33
- opts[:params] || {}.with_indifferent_access
34
- end
35
-
36
- def method
37
- opts[:method] || 'GET'
38
- end
39
-
40
- def filtered_parameters
41
- @filtered_parameters ||= params.merge(filtered: true)
42
- end
43
-
44
- def request_method_symbol
45
- @request_method_symbol ||= method.downcase.to_sym
46
- end
47
-
48
- def remote_ip
49
- opts[:remote_ip] || '192.168.1.1'
50
- end
51
-
52
- def user_agent
53
- opts.key?(:user_agent) ? opts[:user_agent] : CHROME_UA
54
- end
55
- end
56
- end
data/spec/mocks/user.rb DELETED
@@ -1,9 +0,0 @@
1
- module Mocks
2
- class User
3
- attr_accessor :id
4
-
5
- def initialize(id = rand(10_000))
6
- self.id = id
7
- end
8
- end
9
- end
data/spec/spec_helper.rb DELETED
@@ -1,47 +0,0 @@
1
- require 'codeclimate-test-reporter'
2
- CodeClimate::TestReporter.start
3
-
4
- require 'bundler/setup'
5
- Bundler.setup
6
-
7
- require 'rspec/its'
8
- require 'rails'
9
- require 'active_record'
10
- require 'sojourn'
11
-
12
- RSpec.configure do |config|
13
- config.before(:suite) do
14
- ActiveRecord::Base.establish_connection adapter: 'sqlite3',
15
- database: ':memory:'
16
-
17
- ActiveRecord::Schema.define do
18
- self.verbose = false
19
-
20
- create_table :sojourn_events do |t|
21
- t.string :sojourner_uuid, limit: 36, null: false
22
- t.string :name
23
- t.text :properties
24
- t.references :sojourn_request
25
- t.references :user
26
- t.timestamp :created_at
27
- end
28
-
29
- create_table :sojourn_requests do |t|
30
- t.string :host, limit: 2048
31
- t.string :path, limit: 2048
32
- t.string :method
33
- t.string :controller
34
- t.string :action
35
- t.string :ip_address
36
- t.text :user_agent
37
- t.text :params
38
- t.text :referer
39
- t.timestamp :created_at
40
- end
41
- end
42
- end
43
-
44
- config.before do
45
- Sojourn::Event.delete_all
46
- end
47
- end
data/spec/tracker_spec.rb DELETED
@@ -1,110 +0,0 @@
1
- require 'spec_helper'
2
- require 'securerandom'
3
- require 'mocks/controller'
4
-
5
- module Sojourn
6
- COOKIE_NAME = Sojourn.config.cookie_name
7
-
8
- describe Tracker do
9
- let(:user) { Mocks::User.new }
10
- let(:request) { Mocks::Request.new }
11
- let(:cookies) { Mocks::Cookie.new }
12
- let(:ctx) { Mocks::Controller.new(user, request, cookies) }
13
- let(:tracker) { Tracker.new(ctx) }
14
-
15
- describe '#track!' do
16
- let(:event_name) { 'foo' }
17
- let(:opts) { { bar: true } }
18
- before { tracker.track!(event_name, opts) }
19
- subject { Event.last }
20
-
21
- its(:user_id) { is_expected.to eq(user.id) }
22
- its(:name) { is_expected.to eq(event_name) }
23
-
24
- describe 'properties' do
25
- subject { Event.last.properties }
26
-
27
- its(:keys) { is_expected.to eq(%w(request browser bar)) }
28
- its([:request, :params]) { is_expected.to eq('filtered' => true) }
29
- its([:request, :method]) { is_expected.to eq('get') }
30
- its([:browser, :name]) { is_expected.to eq('Chrome') }
31
- end
32
- end
33
-
34
- describe '#sojourning!' do
35
- before { tracker.sojourning! }
36
- subject { Event.last }
37
-
38
- its(:name) { is_expected.to eq('!sojourning') }
39
-
40
- context 'when already tracked once' do
41
- before do
42
- tracker.update_session!
43
- Sojourn::Event.delete_all
44
- tracker.sojourning!
45
- end
46
-
47
- it { is_expected.to be_nil }
48
- end
49
-
50
- context 'when user changes' do
51
- before do
52
- tracker.update_session!
53
- Sojourn::Event.delete_all
54
- ctx.current_user = Mocks::User.new
55
- tracker.sojourning!
56
- end
57
-
58
- its(:name) { is_expected.to eq('!logged_in') }
59
- end
60
-
61
- context 'when user logs out' do
62
- before do
63
- tracker.update_session!
64
- Sojourn::Event.delete_all
65
- ctx.current_user = nil
66
- tracker.sojourning!
67
- end
68
-
69
- its(:name) { is_expected.to eq('!logged_out') }
70
- end
71
-
72
- context 'when user agent not present' do
73
- let(:request) { Mocks::Request.new(user_agent: nil) }
74
-
75
- its(:name) { is_expected.to eq('!sojourning') }
76
-
77
- describe 'properties' do
78
- subject { Event.last.properties }
79
-
80
- its([:browser, :known]) { is_expected.to be(false) }
81
- its([:browser, :name]) { is_expected.to be_nil }
82
- end
83
- end
84
- end
85
-
86
- describe 'update_session!' do
87
- subject { cookies }
88
-
89
- context 'before' do
90
- its([COOKIE_NAME]) { is_expected.to be_nil }
91
- end
92
-
93
- context 'after running' do
94
- before { tracker.update_session! }
95
-
96
- its([COOKIE_NAME, :uuid]) { is_expected.to_not be_nil }
97
- its([COOKIE_NAME, :user_id]) { is_expected.to eq(user.id) }
98
-
99
- context 'with existing cookie' do
100
- let(:sojourner_uuid) { SecureRandom.uuid }
101
- let(:cookie_data) { { user_id: user.id, uuid: sojourner_uuid } }
102
- let(:cookies) { Mocks::Cookie[{ COOKIE_NAME => cookie_data }] }
103
-
104
- its([COOKIE_NAME, :uuid]) { is_expected.to eq(sojourner_uuid) }
105
- its([COOKIE_NAME, :user_id]) { is_expected.to eq(user.id) }
106
- end
107
- end
108
- end
109
- end
110
- end