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.
@@ -0,0 +1,5 @@
1
+ module Referrer
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -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,10 @@
1
+ require_dependency 'referrer/application_controller'
2
+
3
+ module Referrer
4
+ class UsersController < ApplicationController
5
+ def create
6
+ @user = Referrer::User.create!
7
+ render json: {id: @user.id, token: @user.token}
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module Referrer
2
+ module ApplicationHelper
3
+ end
4
+ 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,6 @@
1
+ module Referrer
2
+ class UsersMainAppUser < ActiveRecord::Base
3
+ belongs_to :main_app_user, polymorphic: true
4
+ belongs_to :user
5
+ end
6
+ 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,9 @@
1
+ Referrer::Engine.routes.draw do
2
+ resources :users, only: :create
3
+ resources :sessions, only: :create
4
+ resources :sources, only: [] do
5
+ collection do
6
+ post :mass_create
7
+ end
8
+ end
9
+ end
@@ -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