referrer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|