sojourn 0.0.7 → 0.1.0

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: 05e30ba3490b5cb18a66e3ecf4bdb0048e0ce529
4
- data.tar.gz: 055a2accda2519d306ed020f9df334694cea68c0
3
+ metadata.gz: a5bcd732bdfc1df4fdd68744f4c807f894104848
4
+ data.tar.gz: 927a8ba15bd0948cb8c390c15b7c900785051a1f
5
5
  SHA512:
6
- metadata.gz: 8c8eaa338c482230664097aae7910ebae14f9ebace498dc9feff819d96fce1b66e65546c1416f3e23e3dee6342c0936fe209e081eb11a1a0aeff62de82a74947
7
- data.tar.gz: 70f60a8e7ba6a81055acd0306736b4feff026eed0f2dbac10d18510bd2e6e4dec6711a9c96eacac5eef6767ef12e1ce2b042d0739f31285ec95bdb9286f25f24
6
+ metadata.gz: 469867e717db9bc51b25e1b414a711db552631225524e781a2c9a5d62e3faefdf2d8e0f77acd426aaa7ac094b62e35b7361ec5c761ed4dadaddf7ce8bba2d32b
7
+ data.tar.gz: 44d5346a2ccabc605e220af036077b2f9969213580bc04188852f5d6ee6ff4a2b3a6ea1bf566cefdaa0cde87177092d3892247448bafdc19703cf8f9c96dc3f4
@@ -0,0 +1,11 @@
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/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
+ .DS_Store
@@ -0,0 +1,6 @@
1
+ Metrics/LineLength:
2
+ Max: 99
3
+ Style/AccessModifierIndentation:
4
+ EnforcedStyle: outdent
5
+ Style/Documentation:
6
+ Enabled: false
@@ -0,0 +1,8 @@
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
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Sojourn
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)
2
2
 
3
3
  Simple source & event tracking for Rails. This gem automatically tracks *sojourners*
4
4
  (i.e. unique visitors) based on:
@@ -19,9 +19,7 @@ associated with this UUID and with the current user's ID (if logged-in).
19
19
 
20
20
  Events (`Sojourn::Event`) consist of a name, a set of properties (key-value hash) and information
21
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.
22
+ be queried separately and may have many events. See 'Usage' below for the details of these models.
25
23
 
26
24
 
27
25
  ## Usage
@@ -30,37 +28,17 @@ of these models.
30
28
  # Track a custom event (highly encouraged!):
31
29
  sojourn.track! 'clicked call-to-action', plan_choice: 'enterprise'
32
30
 
31
+ # If you don't have access to a controller context (i.e. the event is not occurring during a web
32
+ # request), you can still track a raw event like this:
33
+
34
+ Sojourn.track_raw_event! 'subscription expired', plan: 'enterprise', customer_id: 'xyb123'
35
+
36
+ # Read events using ActiveRecord
33
37
  e = Sojourn::Event.last
34
38
  e.name # event name (e.g. 'clicked call-to-action')
35
39
  e.sojourner_uuid # uuid tracked across requests, stored in cookie
36
40
  e.user # User or nil
37
41
  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.)
63
-
64
42
  ```
65
43
 
66
44
  ## Default Events
@@ -81,6 +59,42 @@ A `'!sojourning'` event takes place whenever any of the following is true:
81
59
  initializer.)
82
60
 
83
61
 
62
+ ## Properties
63
+
64
+ In addition to properties that you manually add, events will automatically include data about
65
+ the current web request. An example looks like this:
66
+
67
+ ```json
68
+ {
69
+ "custom_property":"value",
70
+ "request":{
71
+ "uuid":"5e698f6ca74a016c49ca6b91a79cada7",
72
+ "host":"example.com",
73
+ "path":"/my-news",
74
+ "controller":"news",
75
+ "action":"index",
76
+ "method":"get",
77
+ "params":{
78
+ "utm_campaign":"daily_updates",
79
+ "page":"1"
80
+ },
81
+ "referer":"https://mail.google.com",
82
+ "ip_address":"42.42.42.42",
83
+ "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"
84
+ },
85
+ "browser":{
86
+ "bot":false,
87
+ "name":"Chrome",
88
+ "known":true,
89
+ "version":"48",
90
+ "platform":"mac"
91
+ },
92
+ "campaign":{
93
+ "utm_campaign":"daily_updates"
94
+ }
95
+ }
96
+ ```
97
+
84
98
  ## Installation
85
99
 
86
100
  Add this line to your application's Gemfile:
@@ -115,4 +129,3 @@ highly scalable data backends.
115
129
  website.
116
130
  * Relies solely on cookies to track visitor UUID across requests (no JS, fingerprinting, etc)
117
131
  * Relies on ActiveRecord for storage. (At a bigger scale, append-only logs are preferred)
118
- * There are no tests.
data/Rakefile CHANGED
@@ -1 +1,9 @@
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(campaigns browsers requests events).map { |m| "create_sojourn_#{m}" }.each do |name|
24
+ %w(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
@@ -3,13 +3,11 @@ class CreateSojournEvents < ActiveRecord::Migration
3
3
  create_table :sojourn_events do |t|
4
4
  t.string :sojourner_uuid, limit: 36, null: false
5
5
  t.string :name
6
- t.text :properties
7
- t.references :sojourn_request
6
+ t.column :properties, :jsonb
8
7
  t.references :user
9
8
  t.timestamp :created_at
10
9
  end
11
10
  add_index :sojourn_events, [:sojourner_uuid]
12
- add_index :sojourn_events, [:sojourn_request_id]
13
11
  add_index :sojourn_events, [:user_id]
14
12
  add_index :sojourn_events, [:created_at]
15
13
  add_index :sojourn_events, [:name]
@@ -16,9 +16,13 @@ module Sojourn
16
16
  end
17
17
 
18
18
  def self.tables_exist?
19
- @tables_exist ||= %w(sojourn_events sojourn_requests sojourn_browsers sojourn_campaigns)
19
+ @tables_exist ||= %w(sojourn_events sojourn_requests)
20
20
  .map { |t| ActiveRecord::Base.connection.table_exists?(t) }.all?
21
21
  end
22
+
23
+ def self.track_raw_event!(name, properties)
24
+ Event.create! sojourner_uuid: '!unknown', name: name, properties: properties
25
+ end
22
26
  end
23
27
 
24
28
  ActionController::Base.send :include, Sojourn::Controller
@@ -1,7 +1,6 @@
1
1
  require_relative 'session_stores/cookie'
2
2
 
3
3
  class Configuration
4
-
5
4
  attr_accessor :campaign_params
6
5
  attr_accessor :session_store
7
6
  attr_accessor :cookie_name
@@ -24,5 +23,4 @@ private
24
23
  self.cookie_name = :_sojourn
25
24
  self.tracking_enabled = true
26
25
  end
27
-
28
26
  end
@@ -2,7 +2,6 @@ require_relative 'tracker'
2
2
 
3
3
  module Sojourn
4
4
  module Controller
5
-
6
5
  def self.included(base)
7
6
  base.before_filter :track_sojourning
8
7
  base.before_filter :save_sojourn_session
@@ -21,6 +20,5 @@ module Sojourn
21
20
  def save_sojourn_session
22
21
  sojourn.update_session!
23
22
  end
24
-
25
23
  end
26
24
  end
@@ -1,13 +1,12 @@
1
+ require_relative 'serializers/indifferent_json'
2
+
1
3
  module Sojourn
2
4
  class Event < ActiveRecord::Base
3
- DEFAULT_FIELDS = [:id, :sojourner_uuid, :name, :properties, :sojourn_request_id, :user_id, :created_at]
5
+ DEFAULT_FIELDS = %i(id sojourner_uuid name properties user_id created_at)
4
6
 
5
- belongs_to :request, foreign_key: :sojourn_request_id
6
7
  belongs_to :user
7
- has_one :campaign, through: :request
8
- has_one :browser, through: :request
9
8
 
10
- serialize :properties
9
+ serialize :properties, Serializers::IndifferentJSON
11
10
 
12
11
  before_save do
13
12
  properties.keys.map(&:to_sym).each do |key|
@@ -1,42 +1,123 @@
1
- require_relative 'campaign'
2
- require_relative 'browser'
3
- require_relative 'serializers/symbol'
1
+ require 'securerandom'
4
2
  require 'addressable/uri'
3
+ require 'browser'
4
+ require 'referer-parser'
5
5
 
6
6
  module Sojourn
7
- class Request < ActiveRecord::Base
8
- attr_accessor :user_agent
7
+ class Request
8
+ KEYS = %w(uuid referer host path controller action params method ip_address user_agent)
9
9
 
10
- serialize :method, Serializers::Symbol
11
- serialize :params
10
+ attr_reader :request
12
11
 
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
12
+ def initialize(request)
13
+ @request = request
14
+ end
16
15
 
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
16
+ def outside_referer?
17
+ referer.present? && referer_host != host
27
18
  end
28
19
 
29
- before_validation do
30
- self.campaign ||= Campaign.from_request(self)
31
- self.browser ||= Browser.from_request(self) if user_agent
20
+ def any_utm_data?
21
+ tracked_param_keys.map { |p| downcased_params[p].present? }.any?
32
22
  end
33
23
 
34
- def outside_referer?
35
- referer.present? && Addressable::URI.parse(referer).host != host
24
+ def tracked_params
25
+ Hash[downcased_params.slice(*tracked_param_keys).delete_if { |_, v| v.blank? }.sort]
36
26
  end
37
27
 
38
- def any_utm_data?
39
- Sojourn.config.campaign_params.map { |p| params[p].present? }.any?
28
+ def raw_data
29
+ Hash[KEYS.map { |k| [k, send(k)] }].with_indifferent_access
30
+ end
31
+
32
+ def browser_data
33
+ return @browser_data if @browser_data
34
+ @browser_data = {
35
+ name: browser.name,
36
+ version: browser.version,
37
+ platform: browser.platform,
38
+ bot: browser.bot?,
39
+ known: browser.known?
40
+ }
41
+ end
42
+
43
+ def referer_data
44
+ return @referer_data if @referer_data
45
+ p = RefererParser::Parser.new.parse(sanitized_referer)
46
+ @referer_data = {
47
+ known: p[:known],
48
+ host: referer_host,
49
+ source: p[:source],
50
+ medium: p[:medium],
51
+ term: p[:term]
52
+ }
53
+ rescue
54
+ @referer_data = {}
55
+ end
56
+
57
+ private
58
+
59
+ def uuid
60
+ @uuid ||= request.uuid || SecureRandom.uuid
61
+ end
62
+
63
+ def referer
64
+ @referer ||= request.referer.try(:truncate, 2048)
65
+ end
66
+
67
+ def host
68
+ @host ||= request.host.try(:truncate, 2048)
69
+ end
70
+
71
+ def path
72
+ @path ||= request.path.try(:truncate, 2048)
73
+ end
74
+
75
+ def controller
76
+ @controller ||= request.params[:controller]
77
+ end
78
+
79
+ def action
80
+ @action ||= request.params[:action]
81
+ end
82
+
83
+ def params
84
+ @params ||= request.filtered_parameters.with_indifferent_access.except(:controller, :action)
85
+ end
86
+
87
+ def method
88
+ @method ||= request.request_method_symbol
89
+ end
90
+
91
+ def ip_address
92
+ @ip_address ||= request.remote_ip
93
+ end
94
+
95
+ def user_agent
96
+ @user_agent ||= request.user_agent
97
+ end
98
+
99
+ def referer_host
100
+ @referer_host ||= parsed_referer.host
101
+ end
102
+
103
+ def sanitized_referer
104
+ @sanitized_referer ||= parsed_referer.display_uri
105
+ end
106
+
107
+ def parsed_referer
108
+ @parsed_referer ||= Addressable::URI.parse(referer)
109
+ end
110
+
111
+ def downcased_params
112
+ params.each_with_object({}) { |(k, v), h| h[k.to_s.downcase] = v }
113
+ end
114
+
115
+ def tracked_param_keys
116
+ Sojourn.config.campaign_params.map(&:to_s).map(&:downcase)
117
+ end
118
+
119
+ def browser
120
+ @browser ||= Browser.new(user_agent: user_agent) if user_agent
40
121
  end
41
122
  end
42
123
  end
@@ -0,0 +1,13 @@
1
+ module Sojourn
2
+ module Serializers
3
+ class IndifferentJSON
4
+ def self.load(string)
5
+ JSON.parse(string || '{}').with_indifferent_access
6
+ end
7
+
8
+ def self.dump(hash)
9
+ (hash || {}).to_json
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,6 @@
1
1
  module Sojourn
2
2
  module SessionStores
3
3
  class Cookie
4
-
5
4
  def initialize(ctx)
6
5
  @ctx = ctx
7
6
  end
@@ -43,7 +42,6 @@ module Sojourn
43
42
  def cookie_name
44
43
  @cookie_name ||= Sojourn.config.cookie_name
45
44
  end
46
-
47
45
  end
48
46
  end
49
47
  end
@@ -13,7 +13,7 @@ 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, request: request,
16
+ Event.create! sojourner_uuid: sojourner_uuid, name: event_name,
17
17
  properties: properties, user_id: user_id
18
18
  end
19
19
 
@@ -37,7 +37,7 @@ module Sojourn
37
37
  private
38
38
 
39
39
  def request
40
- @request ||= Request.from_request(ctx.request)
40
+ @request ||= Request.new(ctx.request)
41
41
  end
42
42
 
43
43
  def session
@@ -70,6 +70,10 @@ module Sojourn
70
70
  Sojourn.config.default_properties_block
71
71
  end
72
72
  @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?
75
+ properties.merge! browser: request.browser_data
76
+ properties.merge! referer: request.referer_data if request.referer_data.any?
73
77
  properties
74
78
  end
75
79
  end
@@ -1,3 +1,3 @@
1
1
  module Sojourn
2
- VERSION = '0.0.7'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -22,7 +22,13 @@ 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'
25
29
 
26
30
  spec.add_dependency 'browser', '>= 0.8.0'
27
31
  spec.add_dependency 'addressable', '>= 2.3.1'
32
+ spec.add_dependency 'referer-parser', '~> 0.3.0'
33
+ spec.add_dependency 'rails', '~> 3.2.0'
28
34
  end
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,56 @@
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[:user_agent] || CHROME_UA
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
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
@@ -0,0 +1,47 @@
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
@@ -0,0 +1,102 @@
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 'request' do
25
+ subject { Event.last.properties[:request] }
26
+
27
+ its([:params]) { is_expected.to eq('filtered' => true) }
28
+ its([:method]) { is_expected.to eq('get') }
29
+ end
30
+
31
+ describe 'properties' do
32
+ subject { Event.last.properties }
33
+
34
+ its(:keys) { is_expected.to eq(%w(request browser bar)) }
35
+ its([:browser, :name]) { is_expected.to eq('Chrome') }
36
+ end
37
+ end
38
+
39
+ describe '#sojourning!' do
40
+ before { tracker.sojourning! }
41
+ subject { Event.last }
42
+
43
+ its(:name) { is_expected.to eq('!sojourning') }
44
+
45
+ context 'when already tracked once' do
46
+ before do
47
+ tracker.update_session!
48
+ Sojourn::Event.delete_all
49
+ tracker.sojourning!
50
+ end
51
+
52
+ it { is_expected.to be_nil }
53
+ end
54
+
55
+ context 'when user changes' do
56
+ before do
57
+ tracker.update_session!
58
+ Sojourn::Event.delete_all
59
+ ctx.current_user = Mocks::User.new
60
+ tracker.sojourning!
61
+ end
62
+
63
+ its(:name) { is_expected.to eq('!logged_in') }
64
+ end
65
+
66
+ context 'when user logs out' do
67
+ before do
68
+ tracker.update_session!
69
+ Sojourn::Event.delete_all
70
+ ctx.current_user = nil
71
+ tracker.sojourning!
72
+ end
73
+
74
+ its(:name) { is_expected.to eq('!logged_out') }
75
+ end
76
+ end
77
+
78
+ describe 'update_session!' do
79
+ subject { cookies }
80
+
81
+ context 'before' do
82
+ its([COOKIE_NAME]) { is_expected.to be_nil }
83
+ end
84
+
85
+ context 'after running' do
86
+ before { tracker.update_session! }
87
+
88
+ its([COOKIE_NAME, :uuid]) { is_expected.to_not be_nil }
89
+ its([COOKIE_NAME, :user_id]) { is_expected.to eq(user.id) }
90
+
91
+ context 'with existing cookie' do
92
+ let(:sojourner_uuid) { SecureRandom.uuid }
93
+ let(:cookie_data) { { user_id: user.id, uuid: sojourner_uuid } }
94
+ let(:cookies) { Mocks::Cookie[{ COOKIE_NAME => cookie_data }] }
95
+
96
+ its([COOKIE_NAME, :uuid]) { is_expected.to eq(sojourner_uuid) }
97
+ its([COOKIE_NAME, :user_id]) { is_expected.to eq(user.id) }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sojourn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Smudge
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-02 00:00:00.000000000 Z
11
+ date: 2016-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,62 @@ dependencies:
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
41
97
  - !ruby/object:Gem::Dependency
42
98
  name: browser
43
99
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +122,34 @@ dependencies:
66
122
  - - ">="
67
123
  - !ruby/object:Gem::Version
68
124
  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
69
153
  description: " Sojourn tracks site visitors and sources, with the ability to recognise
70
154
  multiple sources per visitor. Each time a new source is detected, sojourn tracks
71
155
  the referer, utm data, and logged-in user (if any)"
@@ -75,28 +159,32 @@ executables: []
75
159
  extensions: []
76
160
  extra_rdoc_files: []
77
161
  files:
162
+ - ".codeclimate.yml"
78
163
  - ".gitignore"
164
+ - ".rubocop.yml"
165
+ - ".travis.yml"
79
166
  - Gemfile
80
167
  - README.md
81
168
  - Rakefile
82
169
  - lib/generators/sojourn/install_generator.rb
83
170
  - 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
86
171
  - lib/generators/sojourn/templates/create_sojourn_events.rb
87
- - lib/generators/sojourn/templates/create_sojourn_requests.rb
88
172
  - lib/sojourn.rb
89
- - lib/sojourn/browser.rb
90
- - lib/sojourn/campaign.rb
91
173
  - lib/sojourn/configuration.rb
92
174
  - lib/sojourn/controller.rb
93
175
  - lib/sojourn/event.rb
94
176
  - lib/sojourn/request.rb
95
- - lib/sojourn/serializers/symbol.rb
177
+ - lib/sojourn/serializers/indifferent_json.rb
96
178
  - lib/sojourn/session_stores/cookie.rb
97
179
  - lib/sojourn/tracker.rb
98
180
  - lib/sojourn/version.rb
99
181
  - 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
100
188
  homepage: ''
101
189
  licenses:
102
190
  - ''
@@ -117,9 +205,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
205
  version: '0'
118
206
  requirements: []
119
207
  rubyforge_project:
120
- rubygems_version: 2.2.2
208
+ rubygems_version: 2.4.5.1
121
209
  signing_key:
122
210
  specification_version: 4
123
211
  summary: Simple user source tracking for Rails.
124
- test_files: []
125
- has_rdoc:
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
@@ -1,13 +0,0 @@
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
@@ -1,10 +0,0 @@
1
- class CreateSojournCampaigns < ActiveRecord::Migration
2
- def change
3
- create_table :sojourn_campaigns do |t|
4
- t.string :path, limit: 2048
5
- t.string :params, limit: 2048
6
- t.timestamp :created_at
7
- end
8
- add_index :sojourn_campaigns, [:path, :params]
9
- end
10
- end
@@ -1,19 +0,0 @@
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
@@ -1,27 +0,0 @@
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
@@ -1,27 +0,0 @@
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 (params = tracked_params(request.params)).any?
11
- where(path: request.path.downcase.try(:truncate, 2048))
12
- .where(params: params.to_param.try(:truncate, 2048)).first_or_initialize
13
- end
14
-
15
- private
16
-
17
- def tracked_params(params)
18
- Hash[filter_params(params).sort.map { |k, v| [k.downcase, v.downcase] }]
19
- end
20
-
21
- def filter_params(params)
22
- params.slice(*Sojourn.config.campaign_params).delete_if { |_, v| v.blank? }
23
- end
24
-
25
- end
26
- end
27
- end
@@ -1,13 +0,0 @@
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