referrer 1.0.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +281 -0
- data/Rakefile +26 -0
- data/app/assets/javascripts/referrer/application.js +13 -0
- data/app/assets/javascripts/referrer/referrer.js.erb +364 -0
- data/app/controllers/referrer/application_controller.rb +5 -0
- data/app/controllers/referrer/sessions_controller.rb +22 -0
- data/app/controllers/referrer/sources_controller.rb +31 -0
- data/app/controllers/referrer/users_controller.rb +10 -0
- data/app/helpers/referrer/application_helper.rb +4 -0
- data/app/models/referrer/session.rb +27 -0
- data/app/models/referrer/source.rb +46 -0
- data/app/models/referrer/sources_tracked_object.rb +17 -0
- data/app/models/referrer/user.rb +32 -0
- data/app/models/referrer/users_main_app_user.rb +6 -0
- data/app/views/layouts/referrer/application.html.erb +14 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20160620130847_create_referrer_init_structure.rb +58 -0
- data/lib/concerns/controllers/controller_additions.rb +25 -0
- data/lib/concerns/models/owner_model_additions.rb +35 -0
- data/lib/concerns/models/tracked_model_additions.rb +21 -0
- data/lib/modules/statistics.rb +25 -0
- data/lib/other/markup_generator.rb +112 -0
- data/lib/referrer.rb +25 -0
- data/lib/referrer/engine.rb +5 -0
- data/lib/referrer/version.rb +3 -0
- data/lib/tasks/referrer_tasks.rake +4 -0
- metadata +93 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
require_dependency 'referrer/application_controller'
|
2
|
+
|
3
|
+
module Referrer
|
4
|
+
class SessionsController < ApplicationController
|
5
|
+
def create
|
6
|
+
user = Referrer::User.where(id: session_params[:user_id], token: session_params[:user_token]).first
|
7
|
+
if user
|
8
|
+
@session = user.sessions.active_at(Time.now).first
|
9
|
+
@session = user.sessions.create! unless @session
|
10
|
+
render json: {id: @session.id, active_seconds: @session.active_seconds}
|
11
|
+
else
|
12
|
+
render json: {errors: ['User token is incorrect']}, status: 401
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def session_params
|
19
|
+
params.require(:session).permit(:user_id, :user_token)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_dependency 'referrer/application_controller'
|
2
|
+
|
3
|
+
module Referrer
|
4
|
+
class SourcesController < ApplicationController
|
5
|
+
def mass_create
|
6
|
+
user = Referrer::User.where(id: mass_source_params[:user_id], token: mass_source_params[:user_token]).first
|
7
|
+
if user.present?
|
8
|
+
sessions = user.sessions
|
9
|
+
@sources = JSON.parse(mass_source_params[:values]).inject([]) do |r, pack|
|
10
|
+
session ||= sessions.detect{|s| s.id == pack['session_id'].to_i} ||
|
11
|
+
sessions.detect{|s| s.id == mass_source_params[:current_session_id].to_i}
|
12
|
+
if session.blank? || session.sources.exists?(client_duplicate_id: pack['client_duplicate_id'])
|
13
|
+
r
|
14
|
+
else
|
15
|
+
r << session.sources.create!(entry_point: pack['entry_point'], referrer: pack['referrer'],
|
16
|
+
client_duplicate_id: pack['client_duplicate_id'])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
render json: {ids: @sources.map(&:id)}
|
20
|
+
else
|
21
|
+
render json: {errors: ['User token is incorrect']}, status: 401
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def mass_source_params
|
28
|
+
params.require(:sources).permit(:user_id, :current_session_id, :user_token, :values)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Referrer
|
2
|
+
class Session < ActiveRecord::Base
|
3
|
+
belongs_to :user
|
4
|
+
has_many :sources
|
5
|
+
|
6
|
+
validates_presence_of :user, :active_until
|
7
|
+
|
8
|
+
before_validation :set_active_period
|
9
|
+
|
10
|
+
scope :active_at, -> (time) { where('active_from <= :time AND active_until >= :time', {time: time}) }
|
11
|
+
|
12
|
+
def active_seconds
|
13
|
+
(active_until - Time.now).to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
def source_at(time)
|
17
|
+
sources.priority.where('created_at <= ?', time).last
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_active_period
|
23
|
+
self.active_from = Time.now unless active_from
|
24
|
+
self.active_until = Time.now + Referrer.session_duration unless active_until
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Referrer
|
2
|
+
class Source < ActiveRecord::Base
|
3
|
+
belongs_to :session
|
4
|
+
|
5
|
+
validates_presence_of :entry_point, :utm_source, :utm_campaign, :utm_medium, :utm_content, :utm_term, :session,
|
6
|
+
:kind, :client_duplicate_id
|
7
|
+
|
8
|
+
before_validation :fill_markup_fields, on: :create
|
9
|
+
before_create :set_priority
|
10
|
+
|
11
|
+
scope :priority, -> {where(priority: true)}
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def markup_generator
|
15
|
+
@markup_generator ||= begin
|
16
|
+
mg = MarkupGenerator.new
|
17
|
+
Referrer.markup_generator_settings.each do |k, v|
|
18
|
+
mg.send(:"#{k}=", v)
|
19
|
+
end
|
20
|
+
mg
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_markup
|
26
|
+
attributes.slice(*%w{utm_source utm_campaign utm_medium utm_content utm_term kind})
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def fill_markup_fields
|
32
|
+
markup = self.class.markup_generator.generate(referrer, entry_point)
|
33
|
+
%i{utm_source utm_campaign utm_medium utm_content utm_term kind}.each do |column_name|
|
34
|
+
self.send(:"#{column_name}=", markup[column_name])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_priority
|
39
|
+
previous_priority_source = session.sources.where(priority: true).last
|
40
|
+
if previous_priority_source.blank? ||
|
41
|
+
Referrer.sources_overwriting_schema[kind.to_sym].include?(previous_priority_source.kind)
|
42
|
+
self.priority = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Referrer
|
2
|
+
class SourcesTrackedObject < ActiveRecord::Base
|
3
|
+
belongs_to :trackable, polymorphic: true
|
4
|
+
belongs_to :user
|
5
|
+
belongs_to :source
|
6
|
+
|
7
|
+
validates_presence_of :trackable, :user, :linked_at
|
8
|
+
before_validation :set_fields, on: :create
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def set_fields
|
13
|
+
self.linked_at = Time.now unless linked_at
|
14
|
+
self.source = user.try(:source_at, linked_at) if linked_at
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Referrer
|
2
|
+
class User < ActiveRecord::Base
|
3
|
+
has_many :sessions
|
4
|
+
has_many :users_main_app_users
|
5
|
+
|
6
|
+
validates :token, presence: true
|
7
|
+
|
8
|
+
before_validation :generate_token, on: :create
|
9
|
+
|
10
|
+
def link_with(obj)
|
11
|
+
users_main_app_users.create(main_app_user: obj)
|
12
|
+
end
|
13
|
+
|
14
|
+
def linked_with?(obj)
|
15
|
+
users_main_app_users.where(main_app_user: obj).present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def linked_objects
|
19
|
+
users_main_app_users.includes(:main_app_user).map{|relation| relation.main_app_user}
|
20
|
+
end
|
21
|
+
|
22
|
+
def source_at(time)
|
23
|
+
sessions.active_at(time).first.try(:source_at, time)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def generate_token
|
29
|
+
self.token = SecureRandom.hex(5)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Referrer</title>
|
5
|
+
<%= stylesheet_link_tag "referrer/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "referrer/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
class CreateReferrerInitStructure < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :referrer_users do |t|
|
4
|
+
t.string :token
|
5
|
+
|
6
|
+
t.timestamps null: false
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :referrer_users_main_app_users do |t|
|
10
|
+
t.integer :user_id
|
11
|
+
t.integer :main_app_user_id
|
12
|
+
t.string :main_app_user_type
|
13
|
+
|
14
|
+
t.timestamps null: false
|
15
|
+
end
|
16
|
+
add_index :referrer_users_main_app_users, [:main_app_user_type, :main_app_user_id], name: 'referrer_users_main_app_users_mau'
|
17
|
+
add_index :referrer_users_main_app_users, :user_id
|
18
|
+
|
19
|
+
create_table :referrer_sessions do |t|
|
20
|
+
t.integer :user_id
|
21
|
+
t.datetime :active_from
|
22
|
+
t.datetime :active_until
|
23
|
+
|
24
|
+
t.timestamps null: false
|
25
|
+
end
|
26
|
+
add_index :referrer_sessions, :user_id
|
27
|
+
|
28
|
+
create_table :referrer_sources do |t|
|
29
|
+
t.integer :session_id
|
30
|
+
t.string :referrer
|
31
|
+
t.string :entry_point
|
32
|
+
t.integer :client_duplicate_id
|
33
|
+
t.string :utm_source
|
34
|
+
t.string :utm_campaign
|
35
|
+
t.string :utm_medium
|
36
|
+
t.string :utm_content
|
37
|
+
t.string :utm_term
|
38
|
+
t.string :kind
|
39
|
+
t.boolean :priority, default: false
|
40
|
+
|
41
|
+
t.timestamps null: false
|
42
|
+
end
|
43
|
+
add_index :referrer_sources, :session_id
|
44
|
+
|
45
|
+
create_table :referrer_sources_tracked_objects do |t|
|
46
|
+
t.integer :user_id
|
47
|
+
t.integer :source_id
|
48
|
+
t.datetime :linked_at
|
49
|
+
t.integer :trackable_id
|
50
|
+
t.string :trackable_type
|
51
|
+
|
52
|
+
t.timestamps null: false
|
53
|
+
end
|
54
|
+
add_index :referrer_sources_tracked_objects, [:trackable_type, :trackable_id], name: 'referrer_sources_tracked_objects_ta'
|
55
|
+
add_index :referrer_sources_tracked_objects, :user_id
|
56
|
+
add_index :referrer_sources_tracked_objects, :source_id
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Referrer
|
2
|
+
module ControllerAdditions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_action :link_referrer_user_to_app_user
|
7
|
+
|
8
|
+
def link_referrer_user_to_app_user
|
9
|
+
current_object = send(Referrer.current_user_method_name)
|
10
|
+
if current_object && referrer_user && !referrer_user.linked_with?(current_object)
|
11
|
+
referrer_user.link_with(current_object)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def referrer_user
|
16
|
+
@referrer ||= begin
|
17
|
+
if cookies[:referrer_user].present?
|
18
|
+
obj = JSON.parse(cookies[:referrer_user])
|
19
|
+
Referrer::User.where(id: obj['id'], token: obj['token']).first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Referrer
|
2
|
+
module OwnerModelAdditions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_many :referrer_users_main_app_users, class_name: 'Referrer::UsersMainAppUser', as: :main_app_user
|
7
|
+
|
8
|
+
def referrer_users
|
9
|
+
referrer_users_main_app_users.includes(:user).map{|relation| relation.user}
|
10
|
+
end
|
11
|
+
|
12
|
+
def referrer_sources
|
13
|
+
Referrer::Source.where(
|
14
|
+
session_id: Referrer::Session.where(user_id: referrer_users_main_app_users.pluck(:user_id)).pluck(:id))
|
15
|
+
end
|
16
|
+
|
17
|
+
def referrer_first_source
|
18
|
+
referrer_sources.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def referrer_priority_source
|
22
|
+
referrer_sources.priority.last
|
23
|
+
end
|
24
|
+
|
25
|
+
def referrer_last_source
|
26
|
+
referrer_sources.last
|
27
|
+
end
|
28
|
+
|
29
|
+
def referrer_markups
|
30
|
+
Hash[{first: referrer_first_source, priority: referrer_priority_source,
|
31
|
+
last: referrer_last_source}.map{|k, v| [k, v.try(:to_markup).try(:symbolize_keys!)]}]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Referrer
|
2
|
+
module TrackedModelAdditions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_one :tracked_relation, as: :trackable, class_name: 'Referrer::SourcesTrackedObject'
|
7
|
+
|
8
|
+
def referrer_link_with(r_user, linked_at: nil)
|
9
|
+
create_tracked_relation(user: r_user, linked_at: linked_at)
|
10
|
+
end
|
11
|
+
|
12
|
+
def referrer_link_with!(r_user, linked_at: nil)
|
13
|
+
create_tracked_relation!(user: r_user, linked_at: linked_at)
|
14
|
+
end
|
15
|
+
|
16
|
+
def referrer_markup
|
17
|
+
tracked_relation.try(:source).try(:to_markup).try(:symbolize_keys!)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Referrer
|
2
|
+
module Statistics
|
3
|
+
class << self
|
4
|
+
def sources_markup(from, to)
|
5
|
+
Referrer::Source.where('created_at >= ? AND created_at <= ?', from, to).group_by do |source|
|
6
|
+
source.to_markup.symbolize_keys!
|
7
|
+
end.map do |markup, matches|
|
8
|
+
markup.merge({count: matches.size})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def tracked_objects_markup(from, to, type: nil)
|
13
|
+
result = Referrer::SourcesTrackedObject.where('linked_at >= ? AND linked_at <= ?', from, to).includes(:source)
|
14
|
+
result = result.where(trackable_type: type) if type.present?
|
15
|
+
result.map{|item| item.source}.select do |source|
|
16
|
+
source.present?
|
17
|
+
end.group_by do |source|
|
18
|
+
source.to_markup.symbolize_keys!
|
19
|
+
end.map do |markup, matches|
|
20
|
+
markup.merge({count: matches.size})
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Referrer
|
2
|
+
class MarkupGenerator
|
3
|
+
UTM_KEYS = %w(utm_source utm_medium utm_campaign utm_content utm_term)
|
4
|
+
ORGANICS = [{host: 'search.daum.net', param: 'q'},
|
5
|
+
{host: 'search.naver.com', param: 'query'},
|
6
|
+
{host: 'search.yahoo.com', param: 'p'},
|
7
|
+
{host: /^(www\.)?google\.[a-z]+$/, param: 'q', display: 'google'},
|
8
|
+
{host: 'www.bing.com', param: 'q'},
|
9
|
+
{host: 'search.aol.com', params: 'q'},
|
10
|
+
{host: 'search.lycos.com', param: 'q'},
|
11
|
+
{host: 'edition.cnn.com', param: 'text'},
|
12
|
+
{host: 'index.about.com', param: 'q'},
|
13
|
+
{host: 'mamma.com', param: 'q'},
|
14
|
+
{host: 'ricerca.virgilio.it', param: 'qs'},
|
15
|
+
{host: 'www.baidu.com', param: 'wd'},
|
16
|
+
{host: /^(www\.)?yandex\.[a-z]+$/, param: 'text', display: 'yandex'},
|
17
|
+
{host: 'search.seznam.cz', param: 'oq'},
|
18
|
+
{host: 'www.search.com', param: 'q'},
|
19
|
+
{host: 'search.yam.com', param: 'k'},
|
20
|
+
{host: 'www.kvasir.no', param: 'q'},
|
21
|
+
{host: 'buscador.terra.com', param: 'query'},
|
22
|
+
{host: 'nova.rambler.ru', param: 'query'},
|
23
|
+
{host: 'go.mail.ru', param: 'q'},
|
24
|
+
{host: 'www.ask.com', param: 'q'},
|
25
|
+
{host: 'searches.globososo.com', param: 'q'},
|
26
|
+
{host: 'search.tut.by', param: 'query'}]
|
27
|
+
|
28
|
+
REFERRALS = [{host: /^(www\.)?t\.co$/, display: 'twitter.com'},
|
29
|
+
{host: /^(www\.)?plus\.url\.google\.com$/, display: 'plus.google.com'}]
|
30
|
+
|
31
|
+
attr_accessor :organics, :referrals, :utm_synonyms, :array_params_joiner
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@organics = ORGANICS
|
35
|
+
@referrals = REFERRALS
|
36
|
+
@utm_synonyms = UTM_KEYS.inject({}){|r, key| r.merge({key => []})}
|
37
|
+
@array_params_joiner = ', '
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate(referrer, entry_point)
|
41
|
+
referrer_uri, entry_point_uri = *[referrer, entry_point].map{|url| URI(URI::encode(url || ''))}
|
42
|
+
referrer_params, entry_point_params = *[referrer_uri, entry_point_uri].map{|uri| uri_params(uri)}
|
43
|
+
prepare_result(utm(entry_point_params) || organic(referrer_uri, referrer_params) ||
|
44
|
+
referral(referrer_uri) || direct)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def uri_params(uri)
|
50
|
+
Rack::Utils.parse_query(uri.try(:query))
|
51
|
+
end
|
52
|
+
|
53
|
+
def base_result
|
54
|
+
UTM_KEYS.inject({}){|r, key| r.merge!(key => '(none)')}
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_host(option, value)
|
58
|
+
case option
|
59
|
+
when String
|
60
|
+
option == value
|
61
|
+
when Regexp
|
62
|
+
option =~ value
|
63
|
+
else
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def prepare_result(markup)
|
69
|
+
Hash[markup.map{|k, v| [k, v.is_a?(Array) ? v.join(array_params_joiner) : v]}].symbolize_keys
|
70
|
+
end
|
71
|
+
|
72
|
+
def utm(entry_point_params)
|
73
|
+
if (entry_point_params.keys & (UTM_KEYS + utm_synonyms.values.flatten)).present?
|
74
|
+
UTM_KEYS.inject(base_result) do |r, key|
|
75
|
+
values = if utm_synonyms[key.to_sym].present?
|
76
|
+
[].push(entry_point_params[key]).push([utm_synonyms[key.to_sym]].flatten.map do |synonym_key|
|
77
|
+
entry_point_params[synonym_key]
|
78
|
+
end)
|
79
|
+
else
|
80
|
+
[entry_point_params[key]]
|
81
|
+
end.flatten.compact.map{|value| URI::decode(value)}
|
82
|
+
values.present? ? r.merge!({key => values}) : r
|
83
|
+
end.merge('kind' => 'utm')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def organic(referrer_uri, referrer_params)
|
88
|
+
if referrer_uri.to_s.present?
|
89
|
+
current_organic = organics.detect{|organic| check_host(organic[:host], referrer_uri.host)}
|
90
|
+
base_result.merge!({'utm_source' => current_organic[:display] || current_organic[:host].split('.')[-2],
|
91
|
+
'utm_medium' => 'organic',
|
92
|
+
'utm_term' => referrer_params[current_organic[:param]] || '(none)',
|
93
|
+
'kind' => 'organic'}) if current_organic.present?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def referral(referrer_uri)
|
98
|
+
if referrer_uri.to_s.present?
|
99
|
+
custom_referral = referrals.detect{|referral| check_host(referral[:host], referrer_uri.host)}
|
100
|
+
base_result.merge!(
|
101
|
+
'utm_source' => custom_referral ? custom_referral[:display] : referrer_uri.host.gsub('www.', ''),
|
102
|
+
'utm_medium' => 'referral',
|
103
|
+
'utm_content' => URI::decode(referrer_uri.request_uri) || '(none)',
|
104
|
+
'kind' => 'referral')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def direct
|
109
|
+
base_result.merge!('utm_source' => '(direct)', 'kind' => 'direct')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|