sojourn 0.1.1 → 1.0.0.pre

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.
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