touchpoints 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 90c969ef338bf37fb2169bcea9077339a1ff1302567562c59c63ddf2f6622621
4
+ data.tar.gz: c80e1052ce904c814aa6a52e3afd3ff8c7b057eeba8fbe42d0971c368d0a50a5
5
+ SHA512:
6
+ metadata.gz: e2b35d93675d8dfa395e747a5614c1148f3164d720ba94b76ac4c0e0a059e353afeb3169f3bed29c593ac0911c3682a95aff69c1908889e0e285ef9dce37b4f3
7
+ data.tar.gz: 28f295e6a06ff37c6ab5c6cfafd4b42555101ba36749705601ca358d08eadc345cb09c67b4492a9e5de29f4eb16e42582277387c91e8e3fcf453bcd419af48f9
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in touchpoints.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Daniel Cruz Horts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,36 @@
1
+ A channel is a source of traffic to our application. It could be:
2
+ - direct: users enter our bare URL in their browser. No referrer.
3
+ - organic: users click on a link on a search engine. Referrer is the search engine URL.
4
+ - marketing: users click on a link of an affiliate or paid search result. UTM params are usually set. Referrer is the affiliate URL.
5
+ - campaign: users click on a link of an email/site or enter a URL. UTM params are usually set. Referrer may not.
6
+
7
+ A touchpoint is a visit from the outside world, to our website or set of websites under the same domain.
8
+
9
+ Attribution is the process of assigning a channel to a customer, so we can know what
10
+ channel we'd better put our money on.
11
+
12
+ Usually, the attributed channel is the first touchpoint found 1 month after the user signed up (or any other event we decide).
13
+
14
+ ```
15
+ bin/rails generate migration CreateTouchpoints user_entity_id:uuid utm_params:jsonb referer:string created_at:timestamp
16
+ ```
17
+
18
+ If we have several hosts with same domain operating as a whole, we must share the session cookie between them. In your `config/initializers/session_store.rb`:
19
+
20
+ ```ruby
21
+ Rails.application.config.session_store :cookie_store, key: '_creditspring_session', domain: ENV.fetch('DOMAIN', 'localhost')
22
+ ```
23
+
24
+ In your ApplicationController:
25
+ ```ruby
26
+ include Touchpoints::Tracker
27
+ ```
28
+
29
+ Configure the gem, in `config/initializers/touchpoints.rb`:
30
+ ```ruby
31
+ Touchpoints.configure do |config|
32
+ config.set :logging, true
33
+ config.set :model_id, :entity_id
34
+ config.set :model_foreign_id, :user_entity_id
35
+ end
36
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,86 @@
1
+ module Touchpoints
2
+ module Tracker
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :_track_touchpoints
7
+ end
8
+
9
+ def _track_touchpoints
10
+ touchpoints = Array(session[get(:session_name)])
11
+ .then(&method(:keep_only_recent))
12
+ .then(&method(:add_if_different))
13
+ .then(&method(:persist_if_logged_in))
14
+
15
+ Touchpoints.debug("Touchpoints: #{touchpoints.inspect}")
16
+
17
+ session[get(:session_name)] = touchpoints.last(get(:capacity))
18
+ end
19
+
20
+ private
21
+
22
+ def domain_from(string) # TODO: come up with a more resilient way of extracting the domain
23
+ uri = URI.parse string
24
+ return unless uri.host
25
+
26
+ uri.host.split('.').reverse[0..2].reverse.join('.')
27
+ end
28
+
29
+ def keep_only_recent(touchpoints)
30
+ touchpoints.select { |touchpoint| touchpoint['created_at'] > 60.days.ago }
31
+ end
32
+
33
+ def add_if_different(touchpoints)
34
+ return touchpoints if request.domain == domain_from(request.referer.to_s)
35
+
36
+ last_touchpoint = Hash(touchpoints.last)
37
+ utm_params = params.permit(*get(:utm_params)).to_h
38
+
39
+ new_touchpoint = { 'utm_params' => utm_params, 'referer' => request.referer, 'created_at' => Time.current }
40
+
41
+ Touchpoints.debug("Touchpoint (new): #{new_touchpoint.inspect}")
42
+ Touchpoints.debug("Touchpoint (last): #{last_touchpoint.inspect}")
43
+
44
+ if !equivalent_touchpoints?(new_touchpoint, last_touchpoint)
45
+ touchpoints << new_touchpoint
46
+ Touchpoints.debug('Touchpoint noted!')
47
+ end
48
+
49
+ touchpoints
50
+ end
51
+
52
+ def persist_if_logged_in(touchpoints)
53
+ return touchpoints unless logged_in?
54
+
55
+ last_touchpoint_persisted = get(:model).constantize.where(get(:model_foreign_id) => user_id).last
56
+ last_touchpoint_attributes = last_touchpoint_persisted ? last_touchpoint_persisted.attributes.slice('utm_params', 'referer') : {}
57
+
58
+ touchpoints.each do |touchpoint|
59
+ next if equivalent_touchpoints?(touchpoint, last_touchpoint_attributes)
60
+
61
+ touchpoint[get(:model_foreign_id)] = user_id if logged_in?
62
+ get(:model).constantize.new(touchpoint).save
63
+ Touchpoints.debug('Touchpoint persisted!')
64
+ last_touchpoint_persisted = nil
65
+ end
66
+
67
+ []
68
+ end
69
+
70
+ def logged_in?
71
+ respond_to?(get(:current_user_method)) && send(get(:current_user_method)).present?
72
+ end
73
+
74
+ def user_id
75
+ send(get(:current_user_method)).send(get(:model_id))
76
+ end
77
+
78
+ def equivalent_touchpoints?(a, b)
79
+ a['utm_params'] == b['utm_params'] && a['referer'] == b['referer']
80
+ end
81
+
82
+ def get(option)
83
+ Touchpoints.get(option)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/sal/engine', __FILE__)
6
+
7
+ # Set up gems listed in the Gemfile.
8
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
10
+
11
+ require 'rails/all'
12
+ require 'rails/engine/commands'
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,2 @@
1
+ Touchpoints::Engine.routes.draw do
2
+ end
@@ -0,0 +1,34 @@
1
+ require "touchpoints/engine"
2
+
3
+ module Touchpoints
4
+ @@configuration = {
5
+ session_name: '_touchpoints'.freeze,
6
+ utm_params: %w(utm_source utm_medium utm_campaign utm_term utm_content utm_uid).freeze,
7
+ logging: false,
8
+ model: 'Touchpoint'.freeze,
9
+ model_id: :id,
10
+ model_foreign_id: :user_id,
11
+ current_user_method: :current_user,
12
+ capacity: 22,
13
+ }
14
+
15
+ def self.configure
16
+ yield Touchpoints
17
+
18
+ debug "Touchpoint configuration: #{@@configuration.inspect}"
19
+ end
20
+
21
+ def self.set(option, value)
22
+ @@configuration[option] = value
23
+ end
24
+
25
+ def self.get(option)
26
+ @@configuration[option]
27
+ end
28
+
29
+ def self.debug(message)
30
+ return unless get(:logging)
31
+
32
+ puts message
33
+ end
34
+ end
@@ -0,0 +1,11 @@
1
+ module Touchpoints
2
+ class Engine < ::Rails::Engine
3
+ # We want all the helpers to be used in the app
4
+ #isolate_namespace Touchpoints
5
+
6
+ config.app_generators do |g|
7
+ g.stylesheets false
8
+ g.javascripts false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Touchpoints
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,30 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "touchpoints/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "touchpoints"
8
+ spec.version = Touchpoints::VERSION
9
+ spec.authors = ['Daniel Cruz Horts']
10
+
11
+ spec.summary = %q{Track touchpoints.}
12
+ spec.homepage = 'https://github.com/dncrht/touchpoints'
13
+ spec.license = 'MIT'
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency 'rails', '>= 4'
25
+
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'pry-rails'
28
+ spec.add_development_dependency 'pry-byebug'
29
+ spec.add_development_dependency 'rspec-rails'
30
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: touchpoints
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Cruz Horts
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-05-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - ".gitignore"
90
+ - ".rspec"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - app/controllers/concerns/touchpoints/tracker.rb
96
+ - bin/rails
97
+ - bin/setup
98
+ - config/routes.rb
99
+ - lib/touchpoints.rb
100
+ - lib/touchpoints/engine.rb
101
+ - lib/touchpoints/version.rb
102
+ - touchpoints.gemspec
103
+ homepage: https://github.com/dncrht/touchpoints
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubygems_version: 3.0.3
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Track touchpoints.
126
+ test_files: []