├── spec
├── dummy
│ ├── log
│ │ └── .keep
│ ├── app
│ │ ├── mailers
│ │ │ └── .keep
│ │ ├── models
│ │ │ ├── .keep
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── post.rb
│ │ │ ├── company.rb
│ │ │ ├── request.rb
│ │ │ ├── support.rb
│ │ │ └── user.rb
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ ├── javascripts
│ │ │ │ └── application.js
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── controllers
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── welcome_controller.rb
│ │ │ ├── application_controller.rb
│ │ │ └── posts_controller.rb
│ │ ├── views
│ │ │ ├── welcome
│ │ │ │ └── index.html.erb
│ │ │ └── layouts
│ │ │ │ └── application.html.erb
│ │ └── helpers
│ │ │ └── application_helper.rb
│ ├── lib
│ │ └── assets
│ │ │ └── .keep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── .rspec
│ ├── bin
│ │ ├── rake
│ │ ├── bundle
│ │ ├── rails
│ │ └── setup
│ ├── spec
│ │ ├── support
│ │ │ └── controllers_compatibility.rb
│ │ ├── controllers
│ │ │ ├── users_controller_spec.rb
│ │ │ ├── sessions_controller_spec.rb
│ │ │ └── sources_controller_spec.rb
│ │ ├── features
│ │ │ ├── linking_users_spec.rb
│ │ │ └── js_part_spec.rb
│ │ ├── models
│ │ │ ├── sources_tracked_object_spec.rb
│ │ │ ├── session_spec.rb
│ │ │ ├── user_spec.rb
│ │ │ └── source_spec.rb
│ │ ├── lib
│ │ │ ├── tracked_model_additions_spec.rb
│ │ │ ├── controller_additions_spec.rb
│ │ │ ├── statistics_spec.rb
│ │ │ ├── markup_generator_spec.rb
│ │ │ └── owner_model_additions_spec.rb
│ │ ├── rails_helper.rb
│ │ └── spec_helper.rb
│ ├── config.ru
│ ├── config
│ │ ├── initializers
│ │ │ ├── cookies_serializer.rb
│ │ │ ├── session_store.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── assets.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ └── inflections.rb
│ │ ├── environment.rb
│ │ ├── routes.rb
│ │ ├── boot.rb
│ │ ├── database.yml
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── secrets.yml
│ │ ├── application.rb
│ │ └── environments
│ │ │ ├── development.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ ├── db
│ │ └── migrate
│ │ │ ├── 20160806143430_create_supports.rb
│ │ │ ├── 20160705112156_create_users.rb
│ │ │ ├── 20160708153841_create_companies.rb
│ │ │ ├── 20160723153334_create_requests.rb
│ │ │ └── 20160705114108_create_posts.rb
│ ├── Rakefile
│ └── README.rdoc
└── travis
│ ├── database.travis.yml
│ └── gemfiles
│ ├── Gemfile-5
│ └── Gemfile-4.2
├── lib
├── referrer
│ ├── version.rb
│ └── engine.rb
├── tasks
│ └── referrer_tasks.rake
├── concerns
│ ├── models
│ │ ├── tracked_model_additions.rb
│ │ └── owner_model_additions.rb
│ └── controllers
│ │ └── controller_additions.rb
├── referrer.rb
├── modules
│ └── statistics.rb
└── other
│ └── markup_generator.rb
├── app
├── helpers
│ └── referrer
│ │ └── application_helper.rb
├── controllers
│ └── referrer
│ │ ├── application_controller.rb
│ │ ├── users_controller.rb
│ │ ├── sessions_controller.rb
│ │ └── sources_controller.rb
├── models
│ └── referrer
│ │ ├── users_main_app_user.rb
│ │ ├── sources_tracked_object.rb
│ │ ├── session.rb
│ │ ├── user.rb
│ │ └── source.rb
├── views
│ └── layouts
│ │ └── referrer
│ │ └── application.html.erb
└── assets
│ └── javascripts
│ └── referrer
│ ├── application.js
│ └── referrer.js.erb
├── config
└── routes.rb
├── .gitignore
├── bin
└── rails
├── Rakefile
├── .travis.yml
├── Gemfile
├── referrer.gemspec
├── MIT-LICENSE
├── db
└── migrate
│ └── 20160620130847_create_referrer_init_structure.rb
├── Gemfile.lock
└── README.md
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/welcome/index.html.erb:
--------------------------------------------------------------------------------
1 |
Welcome, test
--------------------------------------------------------------------------------
/lib/referrer/version.rb:
--------------------------------------------------------------------------------
1 | module Referrer
2 | VERSION = '1.0.0'
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/post.rb:
--------------------------------------------------------------------------------
1 | class Post < ActiveRecord::Base
2 | belongs_to :user
3 | end
4 |
--------------------------------------------------------------------------------
/app/helpers/referrer/application_helper.rb:
--------------------------------------------------------------------------------
1 | module Referrer
2 | module ApplicationHelper
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/company.rb:
--------------------------------------------------------------------------------
1 | class Company < ActiveRecord::Base
2 | include Referrer::OwnerModelAdditions
3 | end
4 |
--------------------------------------------------------------------------------
/spec/travis/database.travis.yml:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: <%= ENV['DB'] %>
3 | database: travis_ci_test
4 | timeout: 30000
--------------------------------------------------------------------------------
/spec/dummy/app/models/request.rb:
--------------------------------------------------------------------------------
1 | class Request < ActiveRecord::Base
2 | include Referrer::TrackedModelAdditions
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/support.rb:
--------------------------------------------------------------------------------
1 | class Support < ActiveRecord::Base
2 | include Referrer::TrackedModelAdditions
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/lib/referrer/engine.rb:
--------------------------------------------------------------------------------
1 | module Referrer
2 | class Engine < ::Rails::Engine
3 | isolate_namespace Referrer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/tasks/referrer_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :referrer do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/welcome_controller.rb:
--------------------------------------------------------------------------------
1 | class WelcomeController < ApplicationController
2 | def index; end
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | include Referrer::OwnerModelAdditions
3 | has_many :posts
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/spec/support/controllers_compatibility.rb:
--------------------------------------------------------------------------------
1 | module ControllersCompatibility
2 | def params(hash)
3 | Rails::VERSION::MAJOR > 4 ? {params: hash} : hash
4 | end
5 | end
--------------------------------------------------------------------------------
/app/controllers/referrer/application_controller.rb:
--------------------------------------------------------------------------------
1 | module Referrer
2 | class ApplicationController < ActionController::Base
3 | protect_from_forgery with: :exception
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/app/models/referrer/users_main_app_user.rb:
--------------------------------------------------------------------------------
1 | module Referrer
2 | class UsersMainAppUser < ActiveRecord::Base
3 | belongs_to :main_app_user, polymorphic: true
4 | belongs_to :user
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | resources :posts, only: [:index, :new, :create]
3 | mount Referrer::Engine => '/referrer'
4 |
5 | root to: 'welcome#index'
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160806143430_create_supports.rb:
--------------------------------------------------------------------------------
1 | class CreateSupports < ActiveRecord::Migration
2 | def change
3 | create_table :supports do |t|
4 | t.text :text
5 | t.timestamps
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160705112156_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table :users do |t|
4 | t.string :name
5 |
6 | t.timestamps null: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
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
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160708153841_create_companies.rb:
--------------------------------------------------------------------------------
1 | class CreateCompanies < ActiveRecord::Migration
2 | def change
3 | create_table :companies do |t|
4 | t.string :name
5 |
6 | t.timestamps null: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | Gemfile.lock
3 | .bundle/
4 | log/*.log
5 | pkg/
6 | spec/dummy/db/*.sqlite3
7 | spec/dummy/db/*.sqlite3-journal
8 | spec/dummy/log/*.log
9 | spec/dummy/tmp/
10 | spec/dummy/.sass-cache
11 | spec/dummy/.byebug_history
12 | spec/dummy/public/assets
13 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160723153334_create_requests.rb:
--------------------------------------------------------------------------------
1 | class CreateRequests < ActiveRecord::Migration
2 | def change
3 | create_table :requests do |t|
4 | t.text :body
5 | t.text :email
6 | t.timestamps null: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
6 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20160705114108_create_posts.rb:
--------------------------------------------------------------------------------
1 | class CreatePosts < ActiveRecord::Migration
2 | def change
3 | create_table :posts do |t|
4 | t.string :title
5 | t.text :body
6 | t.integer :user_id
7 |
8 | t.timestamps null: false
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/controllers/referrer/users_controller.rb:
--------------------------------------------------------------------------------
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
11 |
--------------------------------------------------------------------------------
/app/views/layouts/referrer/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Referrer
5 | <%= stylesheet_link_tag "referrer/application", media: "all" %>
6 | <%= javascript_include_tag "referrer/application" %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/travis/gemfiles/Gemfile-5:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'pg'
3 | gem 'mysql2'
4 |
5 | group :development do
6 | gem 'sqlite3'
7 | gem 'rspec-rails', '~> 3.5', require: false
8 | gem 'database_cleaner'
9 | gem 'byebug'
10 | gem 'uglifier'
11 | gem 'watir-webdriver'
12 | gem 'headless'
13 | end
14 |
15 | gem 'rails', '>= 5'
16 |
17 |
--------------------------------------------------------------------------------
/spec/travis/gemfiles/Gemfile-4.2:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'pg'
3 | gem 'mysql2'
4 |
5 | group :development do
6 | gem 'sqlite3'
7 | gem 'rspec-rails', '~> 3.5', require: false
8 | gem 'database_cleaner'
9 | gem 'byebug'
10 | gem 'uglifier'
11 | gem 'watir-webdriver'
12 | gem 'headless'
13 | end
14 |
15 | gem 'rails', '~> 4.2'
16 |
17 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | include Referrer::ControllerAdditions
3 |
4 | # Prevent CSRF attacks by raising an exception.
5 | # For APIs, you may want to use :null_session instead.
6 | protect_from_forgery with: :exception
7 |
8 | def current_user; User.first; end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/posts_controller.rb:
--------------------------------------------------------------------------------
1 | class PostsController < ApplicationController
2 | def index
3 | render json: Post.all.to_json
4 | end
5 |
6 | def create
7 | render json: Post.create!(post_params.merge(user: current_user)).to_json
8 | end
9 |
10 | def new
11 | render json: Post.new.to_json
12 | end
13 |
14 | private
15 |
16 | def post_params
17 | params.require(:post).permit(:title, :body)
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/app/models/referrer/sources_tracked_object.rb:
--------------------------------------------------------------------------------
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
18 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
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/referrer/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'
13 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy/README.rdoc:
--------------------------------------------------------------------------------
1 | == README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
26 |
27 | Please feel free to use a different markup language if you do not plan to run
28 | rake doc:app.
29 |
--------------------------------------------------------------------------------
/spec/dummy/spec/controllers/users_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Referrer::UsersController, type: :controller do
4 | routes { Referrer::Engine.routes }
5 |
6 | describe 'create' do
7 | it 'should be done' do
8 | expect(Referrer::User.all.count).to eq(0)
9 | post(:create)
10 | expect(Referrer::User.all.count).to eq(1)
11 | user = Referrer::User.last
12 | result = JSON.parse(response.body)
13 | expect(result.keys).to eq(%w(id token))
14 | expect(result['id']).to eq(user.id)
15 | expect(result['token']).to eq(user.token)
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | begin
2 | require 'bundler/setup'
3 | rescue LoadError
4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5 | end
6 |
7 | require 'rdoc/task'
8 |
9 | RDoc::Task.new(:rdoc) do |rdoc|
10 | rdoc.rdoc_dir = 'rdoc'
11 | rdoc.title = 'Referrer'
12 | rdoc.options << '--line-numbers'
13 | rdoc.rdoc_files.include('README.rdoc')
14 | rdoc.rdoc_files.include('lib/**/*.rb')
15 | end
16 |
17 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18 | load 'rails/tasks/engine.rake'
19 |
20 |
21 | load 'rails/tasks/statistics.rake'
22 |
23 |
24 |
25 | Bundler::GemHelper.install_tasks
26 |
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/referrer/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require_tree .
14 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/referrer_development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/referrer_test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/referrer_production.sqlite3
26 |
--------------------------------------------------------------------------------
/lib/concerns/models/tracked_model_additions.rb:
--------------------------------------------------------------------------------
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
22 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require_tree .
14 | //= require referrer/application
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | cache: bundler
3 | sudo: false
4 | rvm:
5 | - 2.2.2
6 | gemfile:
7 | - spec/travis/gemfiles/Gemfile-4.2
8 | - spec/travis/gemfiles/Gemfile-5
9 | env:
10 | matrix:
11 | - DB=mysql2
12 | - DB=postgresql
13 | - DB=sqlite3
14 | before_script:
15 | - cp spec/travis/database.travis.yml spec/dummy/config/database.yml
16 | - mysql -e 'create database travis_ci_test'
17 | - psql -c 'create database travis_ci_test' -U postgres
18 | - export DISPLAY=:99.0
19 | - sh -e /etc/init.d/xvfb start
20 | script:
21 | - cd spec/dummy
22 | - RAILS_ENV=test bundle exec rake referrer:install:migrations
23 | - bundle exec rake db:migrate
24 | - RAILS_ENV=test bundle exec rails s -d
25 | - bundle exec rspec
26 |
--------------------------------------------------------------------------------
/app/models/referrer/session.rb:
--------------------------------------------------------------------------------
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
28 |
--------------------------------------------------------------------------------
/app/controllers/referrer/sessions_controller.rb:
--------------------------------------------------------------------------------
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
23 |
--------------------------------------------------------------------------------
/app/models/referrer/user.rb:
--------------------------------------------------------------------------------
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
33 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Declare your gem's dependencies in referrer.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 |
8 | # Declare any dependencies that are still in development here instead of in
9 | # your gemspec. These might include edge Rails or gems from your path or
10 | # Git. Remember to move these dependencies to your gemspec before releasing
11 | # your gem to rubygems.org.
12 |
13 | # To use a debugger
14 | # gem 'byebug', group: [:development, :test]
15 |
16 | group :development do
17 | gem 'sqlite3'
18 | gem 'rspec-rails', '~> 3.5', require: false
19 | gem 'database_cleaner'
20 | gem 'byebug'
21 | gem 'uglifier'
22 | gem 'watir-webdriver'
23 | gem 'headless'
24 | end
25 |
26 |
--------------------------------------------------------------------------------
/spec/dummy/spec/features/linking_users_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Linking users', watir: true do
4 |
5 | # This checks based on def current_user; User.first; end hack in dummy ApplicationController
6 |
7 | it 'should be done' do
8 | user = User.create!
9 | @browser.goto(@url)
10 | Watir::Wait.until{Referrer::Source.count == 1}
11 | @browser.refresh
12 | Watir::Wait.until{Referrer::Source.count == 2}
13 | expect(Referrer::User.count).to eq(1)
14 | expect(Referrer::User.first.linked_objects).to eq([user])
15 | end
16 |
17 | it 'should not be done' do
18 | 2.times do
19 | source_count = Referrer::Source.count
20 | @browser.goto(@url)
21 | Watir::Wait.until{Referrer::Source.count > source_count}
22 | expect(Referrer::User.first.linked_objects).to eq([])
23 | end
24 | end
25 | end
--------------------------------------------------------------------------------
/referrer.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path('../lib', __FILE__)
2 |
3 | require 'referrer/version'
4 |
5 | Gem::Specification.new do |s|
6 | s.name = 'referrer'
7 | s.version = Referrer::VERSION
8 | s.authors = ['Sergey Sokolov']
9 | s.email = ['sokolov.sergey.a@gmail.com']
10 | s.homepage = 'https://github.com/salkar/referrer'
11 | s.summary = "Referrer tracks sources with which users visit your site, computes priority of these sources and provides linking for sources with tracked model's records."
12 | s.description = "Tracking system for sources of site's visitors with sources priorities and linking most priority source with tracked orders/requests/etc"
13 | s.license = 'MIT'
14 |
15 | s.files = Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md']
16 |
17 | s.add_dependency('rails', '>= 4', '< 6')
18 | end
19 |
--------------------------------------------------------------------------------
/lib/concerns/controllers/controller_additions.rb:
--------------------------------------------------------------------------------
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
26 |
--------------------------------------------------------------------------------
/spec/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 |
4 | # path to your application root.
5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
6 |
7 | Dir.chdir APP_ROOT do
8 | # This script is a starting point to setup your application.
9 | # Add necessary setup steps to this file:
10 |
11 | puts "== Installing dependencies =="
12 | system "gem install bundler --conservative"
13 | system "bundle check || bundle install"
14 |
15 | # puts "\n== Copying sample files =="
16 | # unless File.exist?("config/database.yml")
17 | # system "cp config/database.yml.sample config/database.yml"
18 | # end
19 |
20 | puts "\n== Preparing database =="
21 | system "bin/rake db:setup"
22 |
23 | puts "\n== Removing old logs and tempfiles =="
24 | system "rm -f log/*"
25 | system "rm -rf tmp/cache"
26 |
27 | puts "\n== Restarting application server =="
28 | system "touch tmp/restart.txt"
29 | end
30 |
--------------------------------------------------------------------------------
/lib/referrer.rb:
--------------------------------------------------------------------------------
1 | require 'referrer/engine'
2 | require 'other/markup_generator'
3 | require 'concerns/controllers/controller_additions'
4 | require 'concerns/models/tracked_model_additions'
5 | require 'concerns/models/owner_model_additions'
6 | require 'modules/statistics'
7 |
8 | module Referrer
9 | mattr_accessor(:markup_generator_settings){{}}
10 | mattr_accessor(:sources_overwriting_schema) do
11 | {direct: %w(direct),
12 | referral: %w(direct referral organic utm),
13 | organic: %w(direct referral organic utm),
14 | utm: %w(direct referral organic utm)}
15 | end
16 | mattr_accessor(:session_duration){3.months}
17 | mattr_accessor(:current_user_method_name){:current_user}
18 | mattr_accessor(:js_settings) {{}}
19 | mattr_accessor(:js_csrf_token) do
20 | <<-JS
21 | var tokenContainer = document.querySelector("meta[name=csrf-token]");
22 | return tokenContainer ? tokenContainer.content : null;
23 | JS
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/modules/statistics.rb:
--------------------------------------------------------------------------------
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
26 |
--------------------------------------------------------------------------------
/spec/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 13e23782dd59d092731ba667b65672f65967a2688f711a3b4a71316522d7de098d312af59aead858aca405757d9b2aab55da3f3dcd3ad7789a67b589b42e3d12
15 |
16 | test:
17 | secret_key_base: 2af9278e1e1a6e82332cee6e34818f8adcf831b48a5aecaf2006180168f4c517ca24ed828cd738d95520a65b449cf837f99cad81090290d18deb764e9ce0d955
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Sergey Sokolov
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/concerns/models/owner_model_additions.rb:
--------------------------------------------------------------------------------
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
36 |
--------------------------------------------------------------------------------
/app/controllers/referrer/sources_controller.rb:
--------------------------------------------------------------------------------
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
32 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | # Pick the frameworks you want:
4 | require "active_record/railtie"
5 | require "action_controller/railtie"
6 | require "action_mailer/railtie"
7 | require "action_view/railtie"
8 | require "sprockets/railtie"
9 | # require "rails/test_unit/railtie"
10 |
11 | Bundler.require(*Rails.groups)
12 | require "referrer"
13 |
14 | module Dummy
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
22 | # config.time_zone = 'Central Time (US & Canada)'
23 |
24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
26 | # config.i18n.default_locale = :de
27 |
28 | # Do not swallow errors in after_commit/after_rollback callbacks.
29 | config.active_record.raise_in_transactional_callbacks = true if Rails::VERSION::MAJOR < 5
30 | end
31 | end
32 |
33 |
--------------------------------------------------------------------------------
/app/models/referrer/source.rb:
--------------------------------------------------------------------------------
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
47 |
--------------------------------------------------------------------------------
/spec/dummy/spec/controllers/sessions_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 | include ControllersCompatibility
3 |
4 | RSpec.describe Referrer::SessionsController, type: :controller do
5 | routes { Referrer::Engine.routes }
6 |
7 | before :each do
8 | @user = Referrer::User.create!
9 | end
10 |
11 | describe 'create' do
12 | it 'should be done' do
13 | expect(@user.sessions.count).to eq(0)
14 | post(:create, params(session: {user_id: @user.id, user_token: @user.token}))
15 | expect(@user.sessions.count).to eq(1)
16 | session = @user.sessions.last
17 | result = JSON.parse(response.body)
18 | expect(result.keys).to eq(%w(id active_seconds))
19 | expect(result['id']).to eq(session.id)
20 | expect(result['active_seconds']).to be_within(1).of(session.active_seconds)
21 | end
22 |
23 | it 'should be done with already active session' do
24 | session = @user.sessions.create!(active_from: 1.day.ago, active_until: 2.days.since)
25 | expect(@user.sessions.count).to eq(1)
26 | post(:create, params(session: {user_id: @user.id, user_token: @user.token}))
27 | expect(@user.sessions.count).to eq(1)
28 | result = JSON.parse(response.body)
29 | expect(result['id']).to eq(session.id)
30 | end
31 |
32 | it 'should not be done because incorrect token' do
33 | expect(@user.sessions.count).to eq(0)
34 | post(:create, params(session: {user_id: @user.id, user_token: 'test'}))
35 | expect(@user.sessions.count).to eq(0)
36 | result = JSON.parse(response.body)
37 | expect(response.code).to eq('401')
38 | expect(result).to eq({'errors'=>['User token is incorrect']})
39 | end
40 | end
41 | end
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31 | # yet still be able to expire them through the digest params.
32 | config.assets.digest = true
33 |
34 | # Adds additional error checking when serving assets at runtime.
35 | # Checks for improperly declared sprockets dependencies.
36 | # Raises helpful error messages.
37 | config.assets.raise_runtime_errors = true
38 |
39 | # Raises error for missing translations
40 | # config.action_view.raise_on_missing_translations = true
41 | end
42 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/db/migrate/20160620130847_create_referrer_init_structure.rb:
--------------------------------------------------------------------------------
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
59 |
--------------------------------------------------------------------------------
/spec/dummy/spec/models/sources_tracked_object_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Referrer::SourcesTrackedObject, type: :model do
4 | before :each, with_data: true do
5 | @user = Referrer::User.create!
6 | @request = Request.create!
7 | @session = @user.sessions.create!(active_from: 10.days.ago, active_until: 10.days.since)
8 | end
9 |
10 | before :each, with_source: true do
11 | @source = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
12 | client_duplicate_id: 1, created_at: 5.days.ago)
13 | end
14 |
15 | describe 'should be created', with_data: true do
16 | it 'with source', with_source: true do
17 | sto = Referrer::SourcesTrackedObject.create(trackable: @request, user: @user)
18 | expect(sto.new_record?).to eq(false)
19 | expect(sto.user).to eq(@user)
20 | expect(sto.trackable).to eq(@request)
21 | expect(sto.source).to eq(@source)
22 | expect(sto.linked_at.to_i).to be_within(2).of(Time.now.to_i)
23 | end
24 |
25 | it 'without source' do
26 | sto = Referrer::SourcesTrackedObject.create(trackable: @request, user: @user)
27 | expect(sto.new_record?).to eq(false)
28 | expect(sto.user).to eq(@user)
29 | expect(sto.trackable).to eq(@request)
30 | expect(sto.source).to eq(nil)
31 | expect(sto.linked_at.to_i).to be_within(2).of(Time.now.to_i)
32 | end
33 |
34 | it 'with custom linked_at' do
35 | sto = Referrer::SourcesTrackedObject.create(trackable: @request, user: @user, linked_at: 2.days.ago)
36 | expect(sto.new_record?).to eq(false)
37 | expect(sto.linked_at.to_i).to be_within(2).of(2.days.ago.to_i)
38 | end
39 | end
40 |
41 | describe 'should not be created' do
42 | it 'due validations errors' do
43 | sto = Referrer::SourcesTrackedObject.create
44 | expect(sto.new_record?).to eq(true)
45 | expect(sto.errors.size).to be > 0
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static file server for tests with Cache-Control for performance.
16 | if Rails::VERSION::MAJOR > 4
17 | config.public_file_server.enabled = true
18 | config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
19 | else
20 | config.serve_static_files = true
21 | config.static_cache_control = 'public, max-age=3600'
22 | end
23 |
24 | # Show full error reports and disable caching.
25 | config.consider_all_requests_local = true
26 | config.action_controller.perform_caching = false
27 |
28 | # Raise exceptions instead of rendering exception templates.
29 | config.action_dispatch.show_exceptions = false
30 |
31 | # Disable request forgery protection in test environment.
32 | config.action_controller.allow_forgery_protection = false
33 |
34 | # Tell Action Mailer not to deliver emails to the real world.
35 | # The :test delivery method accumulates sent emails in the
36 | # ActionMailer::Base.deliveries array.
37 | config.action_mailer.delivery_method = :test
38 |
39 | # Randomize the order test cases are executed.
40 | config.active_support.test_order = :random
41 |
42 | # Print deprecation notices to the stderr.
43 | config.active_support.deprecation = :stderr
44 |
45 | # Raises error for missing translations
46 | # config.action_view.raise_on_missing_translations = true
47 | end
48 |
--------------------------------------------------------------------------------
/spec/dummy/spec/lib/tracked_model_additions_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Tracked model additions' do
4 | before :each do
5 | @user = Referrer::User.create!
6 | @request = Request.create!
7 | end
8 |
9 | before :each, with_current_source: true do
10 | @session = @user.sessions.create!(active_from: 10.days.ago, active_until: 10.days.since)
11 | @source = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
12 | client_duplicate_id: 1, created_at: 5.days.ago)
13 | end
14 |
15 | describe 'referrer_link_with' do
16 | describe 'should link', with_current_source: true do
17 | it 'by default' do
18 | @request.referrer_link_with(@user)
19 | expect(Referrer::SourcesTrackedObject.count).to eq(1)
20 | relation = Referrer::SourcesTrackedObject.first
21 | expect(relation.trackable).to eq(@request)
22 | expect(relation.user).to eq(@user)
23 | expect(relation.source).to eq(@source)
24 | expect(relation.linked_at.to_i).to be_within(2).of(Time.now.to_i)
25 | end
26 |
27 | it 'with custom linked_at time' do
28 | @request.referrer_link_with(@user, linked_at: 1.day.ago)
29 | expect(Referrer::SourcesTrackedObject.count).to eq(1)
30 | relation = Referrer::SourcesTrackedObject.first
31 | expect(relation.linked_at.to_i).to be_within(2).of(1.day.ago.to_i)
32 | end
33 |
34 | it 'with ! method version' do
35 | @request.referrer_link_with!(@user)
36 | expect(Referrer::SourcesTrackedObject.count).to eq(1)
37 | end
38 | end
39 |
40 | describe 'should not link' do
41 | it 'with default version' do
42 | @request.referrer_link_with(nil)
43 | expect(Referrer::SourcesTrackedObject.count).to eq(0)
44 | end
45 |
46 | it 'with ! method version' do
47 | expect{@request.referrer_link_with!(nil)}.to raise_error(/Validation/)
48 | expect(Referrer::SourcesTrackedObject.count).to eq(0)
49 | end
50 | end
51 | end
52 |
53 | describe 'referrer_markup', with_current_source: true do
54 | it 'should be' do
55 | @request.referrer_link_with(@user)
56 | expect(@request.referrer_markup).to eq({utm_source: 'test.com', utm_campaign: '(none)', utm_medium: 'referral',
57 | utm_content: '/', utm_term: '(none)', kind: 'referral'})
58 | end
59 |
60 | it 'should not be' do
61 | expect(@request.referrer_markup).to eq(nil)
62 | end
63 | end
64 | end
--------------------------------------------------------------------------------
/spec/dummy/spec/models/session_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Referrer::Session, type: :model do
4 | before :each do
5 | @user = Referrer::User.create!
6 | end
7 |
8 | before :each, with_sources: true do
9 | @session = @user.sessions.create!(active_from: 10.days.ago, active_until: 10.days.since)
10 | @source_0 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
11 | client_duplicate_id: 1, created_at: 7.days.ago)
12 | @source_1 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
13 | client_duplicate_id: 2, created_at: 5.days.ago)
14 | @source_2 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
15 | client_duplicate_id: 3, created_at: 2.days.ago)
16 | end
17 |
18 | describe 'scopes' do
19 | describe 'active_at' do
20 | it 'should work' do
21 | @user.sessions.create!(active_from: 10.days.ago, active_until: 7.days.ago)
22 | session_1 = @user.sessions.create!(active_from: 6.days.ago, active_until: 4.days.ago)
23 | @user.sessions.create!(active_from: 3.days.ago, active_until: 1.days.ago)
24 | expect(@user.sessions.active_at(5.days.ago)).to eq([session_1])
25 | end
26 | end
27 | end
28 |
29 | describe 'active period' do
30 | it 'should be correct by default' do
31 | session = @user.sessions.create!
32 | expect(session.active_until.to_i).to be_within(2).of((Time.now + Referrer.session_duration).to_i)
33 | expect(session.active_from.to_i).to be_within(2).of(Time.now.to_i)
34 | end
35 |
36 | it 'should be correct with custom values' do
37 | session = @user.sessions.create!(active_from: 10.days.ago, active_until: 10.days.since)
38 | expect(session.active_until.to_i).to be_within(2).of(10.days.since.to_i)
39 | expect(session.active_from.to_i).to be_within(2).of(10.days.ago.to_i)
40 | end
41 | end
42 |
43 | describe 'active seconds' do
44 | it 'should be correct' do
45 | session = @user.sessions.create!
46 | expect(session.active_seconds).to be_within(2).of(session.active_until - Time.now)
47 | end
48 | end
49 |
50 | describe 'source_at', with_sources: true do
51 | it 'should return correct source' do
52 | expect(@session.source_at(4.days.ago)).to eq(@source_1)
53 | end
54 |
55 | it 'should not return source' do
56 | expect(@session.source_at(8.days.ago)).to eq(nil)
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/spec/dummy/spec/models/user_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Referrer::User, type: :model do
4 | before :each do
5 | @user = Referrer::User.create!
6 | end
7 |
8 | before :each, with_main_app_user: true do
9 | @main_app_user = User.create!
10 | end
11 |
12 | before :each, with_sources: true do
13 | @session = @user.sessions.create!(active_from: 10.days.ago, active_until: 10.days.since)
14 | @source_0 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
15 | client_duplicate_id: 1, created_at: 7.days.ago)
16 | @source_1 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
17 | client_duplicate_id: 2, created_at: 5.days.ago)
18 | @source_2 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com',
19 | client_duplicate_id: 3, created_at: 2.days.ago)
20 | end
21 |
22 | describe 'token' do
23 | it 'should be generated' do
24 | expect(@user.token.length).to be > 0
25 | end
26 | end
27 |
28 | describe 'link_with', with_main_app_user: true do
29 | it 'should work' do
30 | expect(@user.linked_with?(@main_app_user)).to eq(false)
31 | @user.link_with(@main_app_user)
32 | expect(@user.linked_with?(@main_app_user)).to eq(true)
33 | end
34 | end
35 |
36 | describe 'link_with?', with_main_app_user: true do
37 | it 'should return true' do
38 | @user.users_main_app_users.create(main_app_user: @main_app_user)
39 | expect(@user.users_main_app_users.size).to eq(1)
40 | expect(@user.linked_with?(@main_app_user)).to eq(true)
41 | end
42 |
43 | it 'should return false' do
44 | expect(@user.linked_with?(@main_app_user)).to eq(false)
45 | end
46 | end
47 |
48 | describe 'linked_objects' do
49 | it 'should return' do
50 | expect(@user.linked_objects).to eq([])
51 | @user.link_with(@main_app_user)
52 | expect(@user.reload.linked_objects).to eq([@main_app_user])
53 | end
54 |
55 | it 'should not return' do
56 | expect(@user.linked_objects).to eq([])
57 | end
58 | end
59 |
60 | describe 'source_at(time)' do
61 | it 'should return source', with_sources: true do
62 | expect(@user.source_at(4.days.ago)).to eq(@source_1)
63 | end
64 |
65 | it 'should not return source due no suitable sources', with_sources: true do
66 | expect(@session.source_at(8.days.ago)).to eq(nil)
67 | end
68 |
69 | it 'should not return source due no sessions' do
70 | expect(@user.source_at(4.days.ago)).to eq(nil)
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/spec/dummy/spec/lib/controller_additions_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 | include ControllersCompatibility
3 |
4 | RSpec.describe 'Controller additions for' do
5 | describe PostsController, type: :controller do
6 | before :each, with_referrer_user: true do
7 | @referrer_user = Referrer::User.create!
8 | request.cookies['referrer_user'] = {id: @referrer_user.id, token: @referrer_user.token}.to_json
9 | end
10 |
11 | before :each, with_other_referrer_user: true do
12 | @other_referrer_user = Referrer::User.create!
13 | end
14 |
15 | before :each, with_main_app_user: true do
16 | @main_app_user = User.create!
17 | allow_any_instance_of(PostsController).to receive(:current_user).and_return(@main_app_user)
18 | end
19 |
20 | describe 'with main app user', with_main_app_user: true do
21 | describe 'with referrer user', with_referrer_user: true do
22 | describe 'on post request' do
23 | it 'should link users' do
24 | expect(@referrer_user.linked_objects).to eq([])
25 | post(:create, params(post: {title: 'test'}))
26 | expect(@referrer_user.reload.linked_objects).to eq([@main_app_user])
27 | end
28 | end
29 |
30 | describe 'on get request' do
31 | it 'should link users' do
32 | expect(@referrer_user.linked_objects).to eq([])
33 | get(:new)
34 | expect(@referrer_user.reload.linked_objects).to eq([@main_app_user])
35 | end
36 | end
37 | end
38 |
39 | describe 'without referrer user' do
40 | it 'should not link users due no current referrer user', with_other_referrer_user: true do
41 | get(:new)
42 | expect(@other_referrer_user.linked_objects).to eq([])
43 | end
44 |
45 | it 'should not link users due no referrer users' do
46 | post(:create, params(post:{title: 'test'}))
47 | end
48 | end
49 | end
50 |
51 | describe 'without main app user' do
52 | describe 'with referrer user', with_referrer_user: true do
53 | describe 'on post request' do
54 | it 'should not link users' do
55 | expect(@referrer_user.linked_objects).to eq([])
56 | post(:create, params(post:{title: 'test'}))
57 | expect(@referrer_user.linked_objects).to eq([])
58 | end
59 | end
60 |
61 | describe 'on get request' do
62 | it 'should not link users' do
63 | expect(@referrer_user.linked_objects).to eq([])
64 | get(:index)
65 | expect(@referrer_user.linked_objects).to eq([])
66 | end
67 | end
68 | end
69 |
70 | describe 'without referrer user' do
71 | it 'should not link users' do
72 | get(:index)
73 | end
74 | end
75 | end
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/spec/dummy/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV['RAILS_ENV'] ||= 'test'
3 | require File.expand_path('../../config/environment', __FILE__)
4 | # Prevent database truncation if the environment is production
5 | abort("The Rails environment is running in production mode!") if Rails.env.production?
6 | require 'spec_helper'
7 | require 'rspec/rails'
8 | # Add additional requires below this line. Rails is not loaded until this point!
9 |
10 | # Requires supporting ruby files with custom matchers and macros, etc, in
11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
12 | # run as spec files by default. This means that files in spec/support that end
13 | # in _spec.rb will both be required and run as specs, causing the specs to be
14 | # run twice. It is recommended that you do not name files matching this glob to
15 | # end with _spec.rb. You can configure this pattern with the --pattern
16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
17 | #
18 | # The following line is provided for convenience purposes. It has the downside
19 | # of increasing the boot-up time by auto-requiring all files in the support
20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually
21 | # require only the support files necessary.
22 |
23 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
24 |
25 | # Checks for pending migration and applies them before tests are run.
26 | # If you are not using ActiveRecord, you can remove this line.
27 | ActiveRecord::Migration.maintain_test_schema!
28 |
29 | RSpec.configure do |config|
30 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
31 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
32 |
33 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
34 | # examples within a transaction, remove the following line or assign false
35 | # instead of true.
36 | config.use_transactional_fixtures = false
37 |
38 | # RSpec Rails can automatically mix in different behaviours to your tests
39 | # based on their file location, for example enabling you to call `get` and
40 | # `post` in specs under `spec/controllers`.
41 | #
42 | # You can disable this behaviour by removing the line below, and instead
43 | # explicitly tag your specs with their type, e.g.:
44 | #
45 | # RSpec.describe UsersController, :type => :controller do
46 | # # ...
47 | # end
48 | #
49 | # The different available types are documented in the features, such as in
50 | # https://relishapp.com/rspec/rspec-rails/docs
51 | config.infer_spec_type_from_file_location!
52 |
53 | # Filter lines from Rails gems in backtraces.
54 | config.filter_rails_from_backtrace!
55 | # arbitrary gems may also be filtered via:
56 | # config.filter_gems_from_backtrace("gem name")
57 | end
58 |
--------------------------------------------------------------------------------
/spec/dummy/spec/features/js_part_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'JS part', watir: true do
4 | describe 'user' do
5 | it 'should be created' do
6 | expect(Referrer::User.count).to eq(0)
7 | @browser.goto(@url)
8 | Watir::Wait.until{Referrer::User.count == 1}
9 | user = Referrer::User.last
10 | Watir::Wait.until{Referrer::Session.count == 1}
11 | expect(JSON.parse(URI.decode(@browser.cookies.to_a.detect{|c| c[:name] == 'referrer_user'}[:value])))
12 | .to eq({'id' => user.id, 'token' => user.token})
13 | end
14 |
15 | it 'should not be overwritten' do
16 | @browser.goto(@url)
17 | Watir::Wait.until{Referrer::User.count == 1}
18 | user = Referrer::User.last
19 | Watir::Wait.until{Referrer::Session.count == 1}
20 | @browser.refresh
21 | Watir::Wait.until{Referrer::Source.count == 2}
22 | expect(Referrer::User.count).to eq(1)
23 | expect(JSON.parse(URI.decode(@browser.cookies.to_a.detect{|c| c[:name] == 'referrer_user'}[:value])))
24 | .to eq({'id' => user.id, 'token' => user.token})
25 | end
26 | end
27 |
28 | describe 'session' do
29 | it 'should be created' do
30 | expect(Referrer::Session.count).to eq(0)
31 | @browser.goto(@url)
32 | Watir::Wait.until{Referrer::Session.count == 1}
33 | session = Referrer::Session.last
34 | expect(session.user).to eq(Referrer::User.first)
35 | Watir::Wait.until{Referrer::Source.count == 1}
36 | expect(@browser.cookies.to_a.detect{|c| c[:name] == 'referrer_session_id'}[:value]).to eq(session.id.to_s)
37 | end
38 |
39 | it 'should not be overwritten' do
40 | @browser.goto(@url)
41 | Watir::Wait.until{Referrer::Session.count == 1}
42 | session = Referrer::Session.first
43 | Watir::Wait.until{Referrer::Source.count == 1}
44 | @browser.refresh
45 | Watir::Wait.until{Referrer::Source.count == 2}
46 | expect(Referrer::Session.count).to eq(1)
47 | expect(@browser.cookies.to_a.detect{|c| c[:name] == 'referrer_session_id'}[:value]).to eq(session.id.to_s)
48 | end
49 |
50 | it 'should be overwritten due new user' do
51 | @browser.goto(@url)
52 | Watir::Wait.until{Referrer::Session.count == 1}
53 | Watir::Wait.until{@browser.execute_script('return window.referrer.finished;');}
54 | @browser.cookies.delete('referrer_user')
55 | @browser.refresh
56 | Watir::Wait.until{Referrer::Source.count == 2}
57 | expect(Referrer::Session.count).to eq(2)
58 | new_session = Referrer::Session.last
59 | expect(Referrer::User.count).to eq(2)
60 | expect(new_session.user).to eq(Referrer::User.last)
61 | expect(@browser.cookies.to_a.detect{|c| c[:name] == 'referrer_session_id'}[:value]).to eq(new_session.id.to_s)
62 | end
63 | end
64 |
65 | describe 'source' do
66 | it 'should be created' do
67 | @browser.goto(@url)
68 | Watir::Wait.until{Referrer::Source.count == 1}
69 | source = Referrer::Source.last
70 | expect(source.session.present?).to eq(true)
71 | expect(source.referrer).to eq('')
72 | expect(source.entry_point).to eq('http://localhost:3000/')
73 | end
74 | end
75 |
76 | end
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | if Rails::VERSION::MAJOR > 4
26 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
27 | else
28 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
29 | end
30 |
31 | # Compress JavaScripts and CSS.
32 | config.assets.js_compressor = :uglifier
33 | # config.assets.css_compressor = :sass
34 |
35 | # Do not fallback to assets pipeline if a precompiled asset is missed.
36 | config.assets.compile = false
37 |
38 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
39 | # yet still be able to expire them through the digest params.
40 | config.assets.digest = true
41 |
42 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
43 |
44 | # Specifies the header that your server uses for sending files.
45 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
46 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
47 |
48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Use the lowest log level to ensure availability of diagnostic information
52 | # when problems arise.
53 | config.log_level = :debug
54 |
55 | # Prepend all log lines with the following tags.
56 | # config.log_tags = [ :subdomain, :uuid ]
57 |
58 | # Use a different logger for distributed setups.
59 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
60 |
61 | # Use a different cache store in production.
62 | # config.cache_store = :mem_cache_store
63 |
64 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
65 | # config.action_controller.asset_host = 'http://assets.example.com'
66 |
67 | # Ignore bad email addresses and do not raise email delivery errors.
68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
69 | # config.action_mailer.raise_delivery_errors = false
70 |
71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
72 | # the I18n.default_locale when a translation cannot be found).
73 | config.i18n.fallbacks = true
74 |
75 | # Send deprecation notices to registered listeners.
76 | config.active_support.deprecation = :notify
77 |
78 | # Use default logging formatter so that PID and timestamp are not suppressed.
79 | config.log_formatter = ::Logger::Formatter.new
80 |
81 | # Do not dump schema after migrations.
82 | config.active_record.dump_schema_after_migration = false
83 | end
84 |
--------------------------------------------------------------------------------
/spec/dummy/spec/models/source_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Referrer::Source, type: :model do
4 | before :all do
5 | @class = Referrer::Source
6 | end
7 |
8 | before :each do
9 | Referrer.markup_generator_settings = {}
10 | @class.instance_variable_set(:@markup_generator, nil)
11 | end
12 |
13 | before :each, with_session: true do
14 | @user = Referrer::User.create!
15 | @session = @user.sessions.create!
16 | end
17 |
18 | describe 'markup generator' do
19 | it 'should be initialized only once' do
20 | expect(@class.markup_generator.__id__).to eq(@class.markup_generator.__id__)
21 | end
22 |
23 | it 'should be itinialized with custom params' do
24 | custom_params = {
25 | organics: Referrer::MarkupGenerator::ORGANICS << {host: 'search.test.test', param: 'query'},
26 | referrals: Referrer::MarkupGenerator::REFERRALS << {host: /^(www\.)?test1\.test$/, display: 'test1.test'},
27 | utm_synonyms: {utm_campaign: 'custom_campaign'},
28 | array_params_joiner: '|'
29 | }
30 | Referrer.markup_generator_settings = custom_params
31 | mg = @class.markup_generator
32 | custom_params.each do |k, v|
33 | expect(mg.send(k)).to eq(v)
34 | end
35 | end
36 | end
37 |
38 | describe 'utm markup', with_session: true do
39 | it 'should be created before creation' do
40 | source = @session.sources.new(referrer: 'http://test.com/a', entry_point: 'https://www.dummy.com/welcome', client_duplicate_id: 1)
41 | source.save!
42 | expect(source.utm_campaign).to eq('(none)')
43 | expect(source.utm_source).to eq('test.com')
44 | expect(source.utm_medium).to eq('referral')
45 | expect(source.utm_content).to eq('/a')
46 | expect(source.utm_term).to eq('(none)')
47 | expect(source.kind).to eq('referral')
48 | end
49 | end
50 |
51 | describe 'priority', with_session: true do
52 | describe 'for first source' do
53 | it 'should set' do
54 | [['', 'http://dummy.com'],
55 | ['http://test.com', 'http://dummy.com'],
56 | ['http://google.com/?q=query', 'http://dummy.com'],
57 | ['http://test.com/?utm_source=source', 'http://dummy.com']].each do |arr|
58 | @session.sources.destroy_all
59 | source = @session.sources.create!(referrer: arr[0], entry_point: arr[1], client_duplicate_id: 1)
60 | expect(source.priority).to eq(true)
61 | end
62 | end
63 | end
64 |
65 | describe 'for several sources' do
66 | it 'should set' do
67 | @session.sources.create!(referrer: '', entry_point: 'https://www.dummy.com/welcome', client_duplicate_id: 1)
68 | expect(@session.sources.create!(referrer: '', entry_point: 'https://www.dummy.com/welcome', client_duplicate_id: 2).priority).to eq(true)
69 | expect(@session.sources.create!(referrer: 'http://test.com', entry_point: 'https://www.dummy.com/welcome', client_duplicate_id: 3).priority).to eq(true)
70 | end
71 |
72 | it 'should not set' do
73 | @session.sources.create!(referrer: 'http://test.com', entry_point: 'https://www.dummy.com/welcome', client_duplicate_id: 1)
74 | expect(@session.sources.create!(referrer: '', entry_point: 'https://www.dummy.com/welcome', client_duplicate_id: 2).priority).to eq(false)
75 | end
76 | end
77 |
78 | describe 'to_markup' do
79 | it 'should be correct' do
80 | source = @session.sources.create!(referrer: 'http://test.com/a', entry_point: 'https://www.dummy.com/welcome',
81 | client_duplicate_id: 1)
82 | expect(source.to_markup).to eq({'utm_source'=>'test.com', 'utm_campaign'=>'(none)', 'utm_medium'=>'referral',
83 | 'utm_content'=>'/a', 'utm_term'=>'(none)', 'kind'=>'referral'})
84 | end
85 | end
86 | end
87 | end
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | referrer (1.0.0)
5 | rails (>= 4, < 6)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (5.0.0)
11 | actionpack (= 5.0.0)
12 | nio4r (~> 1.2)
13 | websocket-driver (~> 0.6.1)
14 | actionmailer (5.0.0)
15 | actionpack (= 5.0.0)
16 | actionview (= 5.0.0)
17 | activejob (= 5.0.0)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 2.0)
20 | actionpack (5.0.0)
21 | actionview (= 5.0.0)
22 | activesupport (= 5.0.0)
23 | rack (~> 2.0)
24 | rack-test (~> 0.6.3)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (5.0.0)
28 | activesupport (= 5.0.0)
29 | builder (~> 3.1)
30 | erubis (~> 2.7.0)
31 | rails-dom-testing (~> 2.0)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
33 | activejob (5.0.0)
34 | activesupport (= 5.0.0)
35 | globalid (>= 0.3.6)
36 | activemodel (5.0.0)
37 | activesupport (= 5.0.0)
38 | activerecord (5.0.0)
39 | activemodel (= 5.0.0)
40 | activesupport (= 5.0.0)
41 | arel (~> 7.0)
42 | activesupport (5.0.0)
43 | concurrent-ruby (~> 1.0, >= 1.0.2)
44 | i18n (~> 0.7)
45 | minitest (~> 5.1)
46 | tzinfo (~> 1.1)
47 | arel (7.1.0)
48 | builder (3.2.2)
49 | byebug (9.0.5)
50 | childprocess (0.5.9)
51 | ffi (~> 1.0, >= 1.0.11)
52 | concurrent-ruby (1.0.2)
53 | database_cleaner (1.5.3)
54 | diff-lcs (1.2.5)
55 | erubis (2.7.0)
56 | execjs (2.7.0)
57 | ffi (1.9.14)
58 | globalid (0.3.7)
59 | activesupport (>= 4.1.0)
60 | headless (2.2.3)
61 | i18n (0.7.0)
62 | loofah (2.0.3)
63 | nokogiri (>= 1.5.9)
64 | mail (2.6.4)
65 | mime-types (>= 1.16, < 4)
66 | method_source (0.8.2)
67 | mime-types (3.1)
68 | mime-types-data (~> 3.2015)
69 | mime-types-data (3.2016.0521)
70 | mini_portile2 (2.1.0)
71 | minitest (5.9.0)
72 | nio4r (1.2.1)
73 | nokogiri (1.6.8)
74 | mini_portile2 (~> 2.1.0)
75 | pkg-config (~> 1.1.7)
76 | pkg-config (1.1.7)
77 | rack (2.0.1)
78 | rack-test (0.6.3)
79 | rack (>= 1.0)
80 | rails (5.0.0)
81 | actioncable (= 5.0.0)
82 | actionmailer (= 5.0.0)
83 | actionpack (= 5.0.0)
84 | actionview (= 5.0.0)
85 | activejob (= 5.0.0)
86 | activemodel (= 5.0.0)
87 | activerecord (= 5.0.0)
88 | activesupport (= 5.0.0)
89 | bundler (>= 1.3.0, < 2.0)
90 | railties (= 5.0.0)
91 | sprockets-rails (>= 2.0.0)
92 | rails-dom-testing (2.0.1)
93 | activesupport (>= 4.2.0, < 6.0)
94 | nokogiri (~> 1.6.0)
95 | rails-html-sanitizer (1.0.3)
96 | loofah (~> 2.0)
97 | railties (5.0.0)
98 | actionpack (= 5.0.0)
99 | activesupport (= 5.0.0)
100 | method_source
101 | rake (>= 0.8.7)
102 | thor (>= 0.18.1, < 2.0)
103 | rake (11.2.2)
104 | rspec-core (3.5.1)
105 | rspec-support (~> 3.5.0)
106 | rspec-expectations (3.5.0)
107 | diff-lcs (>= 1.2.0, < 2.0)
108 | rspec-support (~> 3.5.0)
109 | rspec-mocks (3.5.0)
110 | diff-lcs (>= 1.2.0, < 2.0)
111 | rspec-support (~> 3.5.0)
112 | rspec-rails (3.5.1)
113 | actionpack (>= 3.0)
114 | activesupport (>= 3.0)
115 | railties (>= 3.0)
116 | rspec-core (~> 3.5.0)
117 | rspec-expectations (~> 3.5.0)
118 | rspec-mocks (~> 3.5.0)
119 | rspec-support (~> 3.5.0)
120 | rspec-support (3.5.0)
121 | rubyzip (1.2.0)
122 | selenium-webdriver (2.53.4)
123 | childprocess (~> 0.5)
124 | rubyzip (~> 1.0)
125 | websocket (~> 1.0)
126 | sprockets (3.7.0)
127 | concurrent-ruby (~> 1.0)
128 | rack (> 1, < 3)
129 | sprockets-rails (3.1.1)
130 | actionpack (>= 4.0)
131 | activesupport (>= 4.0)
132 | sprockets (>= 3.0.0)
133 | sqlite3 (1.3.11)
134 | thor (0.19.1)
135 | thread_safe (0.3.5)
136 | tzinfo (1.2.2)
137 | thread_safe (~> 0.1)
138 | uglifier (3.0.0)
139 | execjs (>= 0.3.0, < 3)
140 | watir-webdriver (0.9.1)
141 | selenium-webdriver (>= 2.46.2)
142 | websocket (1.2.3)
143 | websocket-driver (0.6.4)
144 | websocket-extensions (>= 0.1.0)
145 | websocket-extensions (0.1.2)
146 |
147 | PLATFORMS
148 | ruby
149 |
150 | DEPENDENCIES
151 | byebug
152 | database_cleaner
153 | headless
154 | referrer!
155 | rspec-rails (~> 3.5)
156 | sqlite3
157 | uglifier
158 | watir-webdriver
159 |
160 | BUNDLED WITH
161 | 1.10.6
162 |
--------------------------------------------------------------------------------
/spec/dummy/spec/lib/statistics_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Statistics' do
4 | before :each do
5 | @user = Referrer::User.create!
6 | @session = @user.sessions.create!(active_from: 10.days.ago, active_until: 5.days.ago + 10.minutes)
7 | @session_1 = @user.sessions.create!(active_from: 5.days.ago + 10.minutes)
8 | @session_source = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com/?utm_source=0-0',
9 | client_duplicate_id: 1, created_at: 9.days.ago)
10 | @session_source_1 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com/?utm_source=0-0',
11 | client_duplicate_id: 1, created_at: 7.days.ago)
12 | @session_1_source = @session_1.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com/?utm_source=1-0',
13 | client_duplicate_id: 1, created_at: 5.days.ago + 10.minutes)
14 | @session_1_source_1 = @session_1.sources.create!(referrer: '', entry_point: 'http://dummy.com/',
15 | client_duplicate_id: 1, created_at: 3.days.ago)
16 | end
17 |
18 | describe 'sources_markup' do
19 | it 'should return data' do
20 | result = Referrer::Statistics.sources_markup(10.days.ago, 2.days.ago)
21 | expect(result).
22 | to eq([{utm_source: '0-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
23 | utm_term: '(none)', kind: 'utm', count: 2},
24 | {utm_source: '1-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
25 | utm_term: '(none)', kind: 'utm', count: 1},
26 | {utm_source: '(direct)', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
27 | utm_term: '(none)', kind: 'direct', count: 1}])
28 | end
29 |
30 | it 'should return part of data' do
31 | result = Referrer::Statistics.sources_markup(8.days.ago, 4.days.ago)
32 | expect(result).
33 | to eq([{utm_source: '0-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
34 | utm_term: '(none)', kind: 'utm', count: 1},
35 | {utm_source: '1-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
36 | utm_term: '(none)', kind: 'utm', count: 1}])
37 | end
38 |
39 | it 'should not return data' do
40 | expect(Referrer::Statistics.sources_markup(20.days.ago, 10.days.ago)).to eq([])
41 | end
42 | end
43 |
44 | describe 'tracked_objects_markup' do
45 | before :each do
46 | @user_1 = Referrer::User.create!
47 | @request = Request.create!
48 | @request_1 = Request.create!
49 | @support = Support.create!
50 | @support_1 = Support.create!
51 | Request.create!
52 | Support.create!
53 | @request.referrer_link_with(@user, linked_at: 8.days.ago)
54 | @request_1.referrer_link_with(@user_1, linked_at: 6.days.ago)
55 | @support.referrer_link_with(@user, linked_at: 6.days.ago)
56 | @support_1.referrer_link_with(@user, linked_at: 4.days.ago)
57 | end
58 |
59 | it 'should return data' do
60 | result = Referrer::Statistics.tracked_objects_markup(10.days.ago, 2.days.ago)
61 | expect(result).to eq([{utm_source: '0-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
62 | utm_term: '(none)', kind: 'utm', count: 2},
63 | {utm_source: '1-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
64 | utm_term: '(none)', kind: 'utm', count: 1}])
65 | end
66 |
67 | it 'should return data only for one class' do
68 | result = Referrer::Statistics.tracked_objects_markup(10.days.ago, 2.days.ago, type: 'Request')
69 | expect(result).to eq([{utm_source: '0-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
70 | utm_term: '(none)', kind: 'utm', count: 1}])
71 | end
72 |
73 | it 'should return part of data' do
74 | result = Referrer::Statistics.tracked_objects_markup(7.days.ago, 2.days.ago)
75 | expect(result).to eq([{utm_source: '0-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
76 | utm_term: '(none)', kind: 'utm', count: 1},
77 | {utm_source: '1-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
78 | utm_term: '(none)', kind: 'utm', count: 1}])
79 | end
80 |
81 | it 'should not return data' do
82 | expect(Referrer::Statistics.tracked_objects_markup(20.days.ago, 15.days.ago)).to eq([])
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/lib/other/markup_generator.rb:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/spec/dummy/spec/lib/markup_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Markup generator' do
4 | before :all do
5 | @mg = Referrer::MarkupGenerator.new
6 | end
7 |
8 | describe 'for direct channel' do
9 | it 'should work' do
10 | [[nil, nil],
11 | ['', ''],
12 | [nil, 'http:://dummy.com:3000/'],
13 | ['', 'http:://dummy.com:3000/']].each do |params|
14 | expect(@mg.generate(*params)).to eq({utm_source: '(direct)',
15 | utm_medium: '(none)',
16 | utm_campaign: '(none)',
17 | utm_content: '(none)',
18 | utm_term: '(none)',
19 | kind: 'direct'})
20 | end
21 | end
22 | end
23 |
24 | describe 'for utm channel' do
25 | it 'should work with all params' do
26 | [nil, '', 'http://test.com'].each do |referrer|
27 | expect(@mg.generate(
28 | referrer,
29 | 'http://www.dummy.com/test/?utm_campaign=campaign&utm_medium=medium&utm_source=source&utm_term=term&utm_content=content'
30 | )).to eq({utm_source: 'source',
31 | utm_medium: 'medium',
32 | utm_campaign: 'campaign',
33 | utm_content: 'content',
34 | utm_term: 'term',
35 | kind: 'utm'})
36 | end
37 | end
38 |
39 | it 'should work with several params' do
40 | expect(@mg.generate(
41 | 'http://www.test.com',
42 | 'http://www.dummy.com/test/?utm_campaign=campaign&utm_term=term'
43 | )).to eq({utm_source: '(none)',
44 | utm_medium: '(none)',
45 | utm_campaign: 'campaign',
46 | utm_content: '(none)',
47 | utm_term: 'term',
48 | kind: 'utm'})
49 | end
50 |
51 | describe 'with utm synonyms' do
52 | it 'should work with synonyms and non-synonyms' do
53 | (mg = @mg.clone).utm_synonyms = {utm_campaign: 'custom_campaign', utm_source: %w(source custom_source)}
54 | expect(mg.generate(
55 | 'http://www.test.com',
56 | 'http://www.dummy.com/test/?custom_campaign=campaign&utm_term=term&source=source'
57 | )).to eq({utm_source: 'source',
58 | utm_medium: '(none)',
59 | utm_campaign: 'campaign',
60 | utm_content: '(none)',
61 | utm_term: 'term',
62 | kind: 'utm'})
63 | end
64 |
65 | it 'should work with only synonyms' do
66 | (mg = @mg.clone).utm_synonyms = {utm_campaign: 'custom_campaign', utm_source: %w(source custom_source)}
67 | expect(mg.generate(
68 | 'http://www.test.com',
69 | 'http://www.dummy.com/test/?custom_campaign=campaign&source=source'
70 | )).to eq({utm_source: 'source',
71 | utm_medium: '(none)',
72 | utm_campaign: 'campaign',
73 | utm_content: '(none)',
74 | utm_term: '(none)',
75 | kind: 'utm'})
76 | end
77 | end
78 | end
79 |
80 | describe 'for organic channel' do
81 | it 'should work' do
82 | expect(@mg.generate(
83 | 'https://www.google.ru/search?q=test&oq=test&sourceid=chrome&ie=UTF-8',
84 | 'http://www.dummy.com/'
85 | )).to eq({utm_source: 'google',
86 | utm_medium: 'organic',
87 | utm_campaign: '(none)',
88 | utm_content: '(none)',
89 | utm_term: 'test',
90 | kind: 'organic'})
91 | end
92 | end
93 |
94 | describe 'for referral channel' do
95 | describe 'for default source' do
96 | it 'should work' do
97 | expect(@mg.generate(
98 | 'https://www.sub.test.com/a/b/?param=1#tag',
99 | 'http://www.dummy.com/'
100 | )).to eq({utm_source: 'sub.test.com',
101 | utm_medium: 'referral',
102 | utm_campaign: '(none)',
103 | utm_content: '/a/b/?param=1#tag',
104 | utm_term: '(none)',
105 | kind: 'referral'})
106 | end
107 | end
108 |
109 | describe 'for custom source' do
110 | it 'should work' do
111 | expect(@mg.generate(
112 | 'https://www.t.co/test',
113 | 'http://www.dummy.com/'
114 | )).to eq({utm_source: 'twitter.com',
115 | utm_medium: 'referral',
116 | utm_campaign: '(none)',
117 | utm_content: '/test',
118 | utm_term: '(none)',
119 | kind: 'referral'})
120 | end
121 | end
122 | end
123 | end
--------------------------------------------------------------------------------
/spec/dummy/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'database_cleaner'
2 | require 'byebug'
3 | require 'watir-webdriver'
4 | require 'headless'
5 |
6 |
7 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all
8 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
9 | # The generated `.rspec` file contains `--require spec_helper` which will cause
10 | # this file to always be loaded, without a need to explicitly require it in any
11 | # files.
12 | #
13 | # Given that it is always loaded, you are encouraged to keep this file as
14 | # light-weight as possible. Requiring heavyweight dependencies from this file
15 | # will add to the boot time of your test suite on EVERY test run, even for an
16 | # individual file that may not need all of that loaded. Instead, consider making
17 | # a separate helper file that requires the additional dependencies and performs
18 | # the additional setup, and require it from the spec files that actually need
19 | # it.
20 | #
21 | # The `.rspec` file also contains a few flags that are not defaults but that
22 | # users commonly want.
23 | #
24 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
25 | RSpec.configure do |config|
26 | # rspec-expectations config goes here. You can use an alternate
27 | # assertion/expectation library such as wrong or the stdlib/minitest
28 | # assertions if you prefer.
29 | config.expect_with :rspec do |expectations|
30 | # This option will default to `true` in RSpec 4. It makes the `description`
31 | # and `failure_message` of custom matchers include text for helper methods
32 | # defined using `chain`, e.g.:
33 | # be_bigger_than(2).and_smaller_than(4).description
34 | # # => "be bigger than 2 and smaller than 4"
35 | # ...rather than:
36 | # # => "be bigger than 2"
37 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
38 | end
39 |
40 | # rspec-mocks config goes here. You can use an alternate test double
41 | # library (such as bogus or mocha) by changing the `mock_with` option here.
42 | config.mock_with :rspec do |mocks|
43 | # Prevents you from mocking or stubbing a method that does not exist on
44 | # a real object. This is generally recommended, and will default to
45 | # `true` in RSpec 4.
46 | mocks.verify_partial_doubles = true
47 | end
48 |
49 | # These two settings work together to allow you to limit a spec run
50 | # to individual examples or groups you care about by tagging them with
51 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples
52 | # get run.
53 | config.filter_run :focus
54 | config.run_all_when_everything_filtered = true
55 |
56 | # Allows RSpec to persist some state between runs in order to support
57 | # the `--only-failures` and `--next-failure` CLI options. We recommend
58 | # you configure your source control system to ignore this file.
59 | # config.example_status_persistence_file_path = 'spec/examples.txt'
60 |
61 | # Limits the available syntax to the non-monkey patched syntax that is
62 | # recommended. For more details, see:
63 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
64 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
65 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
66 | config.disable_monkey_patching!
67 |
68 | # Many RSpec users commonly either run the entire suite or an individual
69 | # file, and it's useful to allow more verbose output when running an
70 | # individual spec file.
71 | if config.files_to_run.one?
72 | # Use the documentation formatter for detailed output,
73 | # unless a formatter has already been configured
74 | # (e.g. via a command-line flag).
75 | config.default_formatter = 'doc'
76 | end
77 |
78 | # Print the 10 slowest examples and example groups at the
79 | # end of the spec run, to help surface which specs are running
80 | # particularly slow.
81 | config.profile_examples = 10
82 |
83 | # Run specs in random order to surface order dependencies. If you find an
84 | # order dependency and want to debug it, you can fix the order by providing
85 | # the seed, which is printed after each run.
86 | # --seed 1234
87 | config.order = :random
88 |
89 | # Seed global randomization in this process using the `--seed` CLI option.
90 | # Setting this allows you to use `--seed` to deterministically reproduce
91 | # test failures related to randomization by passing the same `--seed` value
92 | # as the one that triggered the failure.
93 | Kernel.srand config.seed
94 |
95 | config.before(:suite) do
96 | DatabaseCleaner.strategy = :truncation
97 | end
98 |
99 | config.before(:each) do
100 | DatabaseCleaner.clean
101 | end
102 |
103 | config.before(:all, watir: true) do
104 | @url = 'http://localhost:3000/'
105 | end
106 |
107 | config.before(:each, watir: true) do
108 | (@headless = Headless.new).start unless ENV['WITHOUT_HEADLESS']
109 | @browser = Watir::Browser.new(:firefox)
110 | end
111 |
112 | config.after(:each, watir: true) do
113 | @browser.close
114 | @headless.destroy if @headless.present?
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/spec/dummy/spec/controllers/sources_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 | include ControllersCompatibility
3 |
4 | RSpec.describe Referrer::SourcesController, type: :controller do
5 | routes { Referrer::Engine.routes }
6 |
7 | before :each do
8 | @user = Referrer::User.create!
9 | @session = @user.sessions.create!
10 | end
11 |
12 | before :each, with_other_sessions: true do
13 | @other_session = @user.sessions.create!(created_at: Time.now - 1.month)
14 | @other_session_1 = @user.sessions.create!(created_at: Time.now - 1.day)
15 | end
16 |
17 | describe 'mass create' do
18 | it 'should be done' do
19 | expect(@session.sources.count).to eq(0)
20 | post(:mass_create, params(sources: {current_session_id: @session.id, user_token: @user.token, user_id: @user.id, values: [{
21 | referrer: 'http://test.com', entry_point: 'http://dummy.com', client_duplicate_id: 1}].to_json}))
22 | expect(@session.sources.count).to eq(1)
23 | source = @session.sources.last
24 | result = JSON.parse(response.body)
25 | expect(result.keys).to eq(%w(ids))
26 | expect(result['ids']).to eq([source.id])
27 | expect(source.kind).to eq('referral')
28 | end
29 |
30 | it 'should not be done because incorrect token' do
31 | expect(@session.sources.count).to eq(0)
32 | post(:mass_create, params(sources: {current_session_id: @session.id, user_token: 'test', user_id: @user.id, values: [{
33 | referrer: 'http://test.com', entry_point: 'http://dummy.com', client_duplicate_id: 1}].to_json}))
34 | expect(@session.sources.count).to eq(0)
35 | result = JSON.parse(response.body)
36 | expect(response.code).to eq('401')
37 | expect(result).to eq({'errors'=>['User token is incorrect']})
38 | end
39 |
40 | it 'should be done for source with session id', with_other_sessions: true do
41 | expect(@session.sources.count).to eq(0)
42 | post(:mass_create, params(sources: {current_session_id: @session.id,
43 | user_token: @user.token,
44 | user_id: @user.id,
45 | values: [{
46 | referrer: 'http://test.com', entry_point: 'http://dummy.com',
47 | client_duplicate_id: 1, session_id: @other_session.id
48 | }].to_json}))
49 | expect(@other_session.sources.count).to eq(1)
50 | source = @other_session.sources.last
51 | expect(source.session_id).to eq(@other_session.id)
52 | end
53 |
54 | it 'should be done with several source values' do
55 | expect(@session.sources.count).to eq(0)
56 | post(:mass_create, params(sources: {current_session_id: @session.id,
57 | user_token: @user.token,
58 | user_id: @user.id,
59 | values: [{referrer: 'http://test.com', entry_point: 'http://dummy.com', client_duplicate_id: 1},
60 | {referrer: 'http://test2.com', entry_point: 'http://dummy2.com', client_duplicate_id: 2}
61 | ].to_json}))
62 | expect(@session.sources.count).to eq(2)
63 | referrers = @session.sources.pluck(:referrer)
64 | %w{http://test2.com http://test.com}.each do |referrer|
65 | expect(referrers.include?(referrer)).to eq(true)
66 | end
67 | end
68 |
69 | describe 'with duplicates' do
70 | it 'in params should not be done' do
71 | expect(@session.sources.count).to eq(0)
72 | post(:mass_create, params(sources: {current_session_id: @session.id,
73 | user_token: @user.token,
74 | user_id: @user.id,
75 | values: [{referrer: 'http://test.com', entry_point: 'http://dummy.com', client_duplicate_id: 1},
76 | {referrer: 'http://test2.com', entry_point: 'http://dummy2.com', client_duplicate_id: 1}
77 | ].to_json}))
78 | expect(@session.sources.count).to eq(1)
79 | source = @session.sources.last
80 | expect(source.referrer).to eq('http://test.com')
81 | end
82 |
83 | it 'in db should not be done' do
84 | source = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com', client_duplicate_id: 1)
85 | post(:mass_create, params(sources: {current_session_id: @session.id,
86 | user_token: @user.token,
87 | user_id: @user.id,
88 | values: [{
89 | referrer: 'http://test2.com', entry_point: 'http://dummy2.com',
90 | client_duplicate_id: 1
91 | }].to_json}))
92 | @session.sources.reload
93 | expect(@session.sources.count).to eq(1)
94 | expect(Referrer::Source.count).to eq(1)
95 | s = @session.sources.last
96 | expect(s.id).to eq(source.id)
97 | end
98 | end
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/spec/dummy/spec/lib/owner_model_additions_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Owner model additions' do
4 | %w{User Company}.each do |main_app_user_class|
5 | describe "with main app user #{main_app_user_class}" do
6 | before :each do
7 | @main_app_user = Object.const_get(main_app_user_class).create!
8 | @user = Referrer::User.create!
9 | end
10 |
11 | before :each, with_users_relation: true do
12 | @user.link_with(@main_app_user)
13 | end
14 |
15 | before :each, with_sessions: true do
16 | @session = @user.sessions.create!(active_from: 10.days.ago)
17 | @session_1 = @user.sessions.create!(active_from: 5.days.ago)
18 | end
19 |
20 | before :each, with_sources: true do
21 | @session_source = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com/?utm_source=0-0',
22 | client_duplicate_id: 1, created_at: 9.days.ago)
23 | @session_source_1 = @session.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com/?utm_source=0-1',
24 | client_duplicate_id: 1, created_at: 7.days.ago)
25 | @session_1_source = @session_1.sources.create!(referrer: 'http://test.com', entry_point: 'http://dummy.com/?utm_source=1-0',
26 | client_duplicate_id: 1, created_at: 5.days.ago + 10.minutes)
27 | @session_1_source_1 = @session_1.sources.create!(referrer: '', entry_point: 'http://dummy.com/',
28 | client_duplicate_id: 1, created_at: 4.days.ago)
29 | end
30 |
31 | describe 'relations' do
32 | describe 'referrer_users_main_app_users' do
33 | it 'should return main app user', with_users_relation: true do
34 | expect(@main_app_user.referrer_users_main_app_users.size).to eq(1)
35 | relation = @main_app_user.referrer_users_main_app_users.first
36 | expect(relation.user_id).to eq(@user.id)
37 | end
38 |
39 | it 'should not return associated users' do
40 | expect(@main_app_user.referrer_users_main_app_users).to eq([])
41 | end
42 | end
43 | end
44 |
45 | describe 'referrer_users' do
46 | it 'should be returned', with_users_relation: true do
47 | expect(@main_app_user.referrer_users).to eq([@user])
48 | end
49 |
50 | it 'should not be returned' do
51 | expect(@main_app_user.referrer_users).to eq([])
52 | end
53 | end
54 |
55 | describe 'referrer_sources' do
56 | it 'should be returned', with_users_relation: true, with_sessions: true, with_sources: true do
57 | expect(@main_app_user.referrer_sources).to eq([@session_source, @session_source_1, @session_1_source, @session_1_source_1])
58 | end
59 |
60 | it 'should not be returned without sources', with_users_relation: true, with_sessions: true do
61 | expect(@main_app_user.referrer_sources).to eq([])
62 | end
63 |
64 | it 'should not be returned without sessions', with_users_relation: true do
65 | expect(@main_app_user.referrer_sources).to eq([])
66 | end
67 |
68 | it 'should not be returned without referrer users' do
69 | expect(@main_app_user.referrer_sources).to eq([])
70 | end
71 | end
72 |
73 | describe 'referrer sources block', with_users_relation: true, with_sessions: true do
74 | describe 'referrer_first_source' do
75 | it 'should be returned', with_sources: true do
76 | expect(@main_app_user.referrer_first_source).to eq(@session_source)
77 | end
78 |
79 | it 'should not be returned' do
80 | expect(@main_app_user.referrer_first_source).to eq(nil)
81 | end
82 | end
83 |
84 | describe 'referrer_priority_source' do
85 | it 'should be returned', with_sources: true do
86 | expect(@main_app_user.referrer_priority_source).to eq(@session_1_source)
87 | end
88 |
89 | it 'should not be returned' do
90 | expect(@main_app_user.referrer_priority_source).to eq(nil)
91 | end
92 | end
93 |
94 | describe 'referrer_last_source' do
95 | it 'should be returned', with_sources: true do
96 | expect(@main_app_user.referrer_last_source).to eq(@session_1_source_1)
97 | end
98 |
99 | it 'should not be returned' do
100 | expect(@main_app_user.referrer_last_source).to eq(nil)
101 | end
102 | end
103 |
104 | describe 'referrer_markups', with_users_relation: true, with_sessions: true do
105 | it 'should return correct data', with_sources: true do
106 | expect(@main_app_user.referrer_markups).
107 | to eq({first: {utm_source: '0-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
108 | utm_term: '(none)', kind: 'utm'},
109 | priority: {utm_source: '1-0', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
110 | utm_term: '(none)', kind: 'utm'},
111 | last: {utm_source: '(direct)', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
112 | utm_term: '(none)', kind: 'direct'}})
113 | end
114 |
115 | it 'should return empty data' do
116 | expect(@main_app_user.referrer_markups).to eq({first: nil, priority: nil, last: nil})
117 | end
118 | end
119 | end
120 | end
121 | end
122 | end
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Referrer
2 |
3 | [](http://travis-ci.org/salkar/referrer)
4 | [](https://codeclimate.com/github/salkar/referrer)
5 |
6 | Referrer tracks sources with which users visit your site, converts them into utm markup, computes priority of these sources and provides linking for sources with tracked model's records (orders/requests/etc).
7 |
8 | Sample questions which Referrer can help to answer:
9 |
10 | - Where did this user come from?
11 | - `{utm_source: 'google', utm_medium: 'organic', utm_campaign: '(none)', utm_content: '(none)', utm_term: 'user search query', kind: 'organic'}`
12 |
13 | - Where did users come from last month?
14 | - `[{utm_source: 'google', utm_medium: 'organic', utm_campaign: '(none)', utm_content: '(none)', utm_term: 'user search query', kind: 'organic', count: 1}, {utm_source: 'google', utm_campaign: 'adv_campaign', utm_medium: 'cpc', utm_content: 'adv 1', utm_term: 'some text', kind: 'utm', count: 2}, etc...]`
15 |
16 | - Where did user who make purchase come from?
17 | - `{utm_source: 'twitter.com', utm_medium: 'referral', utm_campaign: '(none)', utm_content: '/some_path', utm_term: '(none)', kind: 'referral'}`
18 |
19 | - Where did users who left some requests come from last week?
20 | - `[{utm_source: '(direct)', utm_medium: '(none)', utm_campaign: '(none)', utm_content: '(none)', utm_term: '(none)', kind: 'direct', count: 3}, {utm_source: 'google', utm_campaign: 'adv_campaign', utm_medium: 'cpc', utm_content: 'adv 2', utm_term: 'another text', kind: 'utm', count: 5}, etc...]`
21 |
22 | ## Installation
23 |
24 | ### Basic setup
25 |
26 | 1. Add Referrer to your Gemfile:
27 | ```ruby
28 | gem 'referrer'
29 | ```
30 |
31 | 2. Run the bundle command to install it:
32 | ```bash
33 | bundle install
34 | ```
35 |
36 | 3. Copy migrations from engine:
37 | ```bash
38 | rake referrer:install:migrations
39 | ```
40 |
41 | 4. Migrate your database:
42 | ```bash
43 | rake db:migrate
44 | ```
45 |
46 | 5. Mount engine's routes at `config/routes.rb`
47 | ```ruby
48 | mount Referrer::Engine => '/referrer'
49 | ```
50 |
51 | 6. Require engine's js in your `application.js` file:
52 | ```ruby
53 | //= require referrer/application
54 | ```
55 |
56 | 7. Add to your ApplicationController:
57 | ```ruby
58 | include Referrer::ControllerAdditions
59 | ```
60 |
61 | 8. If your `current_user` method has another name, add to `config/initializers/referrer.rb`:
62 | ```ruby
63 | Referrer.current_user_method_name = :your_method_name
64 | ```
65 |
66 | ### Setup your statistics owners
67 |
68 | Add to your models which objects may be returned from `current_user` (or your same method, specified in `Referrer.current_user_method_name`) `include Referrer::OwnerModelAdditions`. For sample if your `current_user` return `User` objects:
69 |
70 | ```ruby
71 | class User < ActiveRecord::Base
72 | include Referrer::OwnerModelAdditions
73 | #...
74 | end
75 | ```
76 |
77 | ### Setup your tracked models
78 |
79 | 1. Add to models you want to track `include Referrer::TrackedModelAdditions`. For sample if you want to track where users who make orders come from (`Order` class):
80 | ```ruby
81 | class Order < ActiveRecord::Base
82 | include Referrer::TrackedModelAdditions
83 | #...
84 | end
85 | ```
86 |
87 | 2. After you create tracked model record next time you can link record with current source. For sample (with `Order` object):
88 | ```ruby
89 | # OrdersControllers#create
90 | #...
91 | if @order.save
92 | @order.referrer_link_with(referrer_user) # referrer_user defined in Referrer::TrackedModelAdditions
93 | else
94 | #...
95 | ```
96 |
97 | ## Settings
98 |
99 | Referrer settings can be changed in initializers. For sample, you can create `config/initializers/referrer.rb` and add your custom settings to it.
100 |
101 | ###Settings list
102 |
103 | 1. **current_user_method_name** - method name in ApplicationController which return current logged in object.
104 | ```ruby
105 | :current_user
106 | ```
107 |
108 | 2. **js_settings** - options passes to js part of Referrer.
109 | ```ruby
110 | {}
111 | ```
112 | Available options:
113 | * cookies
114 |
115 | ```javascript
116 | {prefix: 'referrer',
117 | domain: null,
118 | path: '/'}
119 | ```
120 |
121 | * object
122 |
123 | ```javascript
124 | {name: 'referrer'}
125 | ```
126 |
127 | * callback
128 |
129 | ```javascript
130 | null
131 | ```
132 |
133 | 3. **js_csrf_token** - js code to get CSRF token if it is used.
134 | ```ruby
135 | <<-JS
136 | var tokenContainer = document.querySelector("meta[name=csrf-token]");
137 | return tokenContainer ? tokenContainer.content : null;
138 | JS
139 | ```
140 |
141 | 4. **markup_generator_settings** - options for Referrer::MarkupGenerator.
142 | ```ruby
143 | {}
144 | ```
145 | Available options:
146 | * organics
147 |
148 | ```ruby
149 | [{host: 'search.daum.net', param: 'q'},
150 | {host: 'search.naver.com', param: 'query'},
151 | {host: 'search.yahoo.com', param: 'p'},
152 | {host: /^(www\.)?google\.[a-z]+$/, param: 'q', display: 'google'},
153 | {host: 'www.bing.com', param: 'q'},
154 | {host: 'search.aol.com', params: 'q'},
155 | {host: 'search.lycos.com', param: 'q'},
156 | {host: 'edition.cnn.com', param: 'text'},
157 | {host: 'index.about.com', param: 'q'},
158 | {host: 'mamma.com', param: 'q'},
159 | {host: 'ricerca.virgilio.it', param: 'qs'},
160 | {host: 'www.baidu.com', param: 'wd'},
161 | {host: /^(www\.)?yandex\.[a-z]+$/, param: 'text', display: 'yandex'},
162 | {host: 'search.seznam.cz', param: 'oq'},
163 | {host: 'www.search.com', param: 'q'},
164 | {host: 'search.yam.com', param: 'k'},
165 | {host: 'www.kvasir.no', param: 'q'},
166 | {host: 'buscador.terra.com', param: 'query'},
167 | {host: 'nova.rambler.ru', param: 'query'},
168 | {host: 'go.mail.ru', param: 'q'},
169 | {host: 'www.ask.com', param: 'q'},
170 | {host: 'searches.globososo.com', param: 'q'},
171 | {host: 'search.tut.by', param: 'query'}]
172 | ```
173 | * referrals
174 |
175 | ```ruby
176 | [{host: /^(www\.)?t\.co$/, display: 'twitter.com'},
177 | {host: /^(www\.)?plus\.url\.google\.com$/, display: 'plus.google.com'}]
178 | ```
179 | * utm_synonyms
180 |
181 | ```ruby
182 | {'utm_source'=>[], 'utm_medium'=>[], 'utm_campaign'=>[], 'utm_content'=>[], 'utm_term'=>[]}
183 | ```
184 | * array_params_joiner
185 |
186 | ```ruby
187 | ', '
188 | ```
189 |
190 | 5. **session_duration** - after this duration left, new session will be created and its sources priorities will be computed without regard to past sessions sources.
191 | ```ruby
192 | 3.months
193 | ```
194 |
195 | 6. **sources_overwriting_schema** - source's kind priorities for priority source computation.
196 | ```ruby
197 | {direct: %w(direct),
198 | referral: %w(direct referral organic utm),
199 | organic: %w(direct referral organic utm),
200 | utm: %w(direct referral organic utm)}
201 | ```
202 |
203 | ## Usage
204 |
205 | ### Sources owner
206 |
207 | #### referrer_markups
208 |
209 | Get markups for application user. Returns hash where:
210 | * **first** - user's first source
211 | * **priority** - user's last priority source, computed using `sources_overwriting_schema`
212 | * **last** - user's last source
213 |
214 | ```ruby
215 | user.referrer_markups
216 | => {first: {utm_source: 'google', utm_medium: 'organic', utm_campaign: '(none)', utm_content: '(none)', utm_term: 'user search query', kind: 'organic'},
217 | priority: {utm_source: 'google', utm_campaign: 'adv_campaign', utm_medium: 'cpc', utm_content: 'adv 1', utm_term: 'some text', kind: 'utm'},
218 | last: {utm_source: '(direct)', utm_medium: '(none)', utm_campaign: '(none)', utm_content: '(none)', utm_term: '(none)', kind: 'direct'}}
219 | ```
220 |
221 | ### Tracked model
222 |
223 | #### referrer_markup
224 |
225 | Get markup for tracked model's object. Returns markup hash:
226 |
227 | ```ruby
228 | user.referrer_markup
229 | => {utm_source: 'test.com', utm_campaign: '(none)', utm_medium: 'referral', utm_content: '/',
230 | utm_term: '(none)', kind: 'referral'}}
231 | ```
232 |
233 | #### referrer_link_with(r_user, linked_at: nil) [referrer_link_with!(r_user, linked_at: nil)]
234 |
235 | Link tracked model's object to referrer's data. Parameters:
236 |
237 | * `r_user` - `Referrer::User` object, in controller can be got by `referrer_user` (requires Referrer::ControllerAdditions be included).
238 | * `linked_at` - custom linking `DateTime`.
239 |
240 | ### Statistics
241 |
242 | #### Referrer::Statistics.sources_markup(from, to)
243 |
244 | Get markup of visits occurred in the specified period. Parameters:
245 |
246 | * `from` - DateTime of period start
247 | * `to` - DateTime of period end
248 |
249 | Sample:
250 |
251 | ```ruby
252 | Referrer::Statistics.sources_markup(10.days.ago, 2.days.ago)
253 | => [{utm_source: 'first', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
254 | utm_term: '(none)', kind: 'utm', count: 2},
255 | {utm_source: 'second', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
256 | utm_term: '(none)', kind: 'utm', count: 1},
257 | {utm_source: '(direct)', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
258 | utm_term: '(none)', kind: 'direct', count: 1}]
259 | ```
260 |
261 | #### Referrer::Statistics.tracked_objects_markup(from, to, type: nil)
262 |
263 | Get markup associated with tracked model objects, in the specified period. Parameters:
264 |
265 | * `from` - DateTime of period start
266 | * `to` - DateTime of period end
267 | * `type` - tracked model name. If specified, response will contain markup associated only with this model objects.
268 |
269 | Sample:
270 |
271 | ```ruby
272 | Referrer::Statistics.tracked_objects_markup(10.days.ago, 2.days.ago)
273 | => [{utm_source: 'first', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
274 | utm_term: '(none)', kind: 'utm', count: 2},
275 | {utm_source: 'second', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
276 | utm_term: '(none)', kind: 'utm', count: 1}]
277 | ```
278 |
279 | ## License
280 |
281 | This project rocks and uses MIT-LICENSE.
282 |
--------------------------------------------------------------------------------
/app/assets/javascripts/referrer/referrer.js.erb:
--------------------------------------------------------------------------------
1 | <% urls = Referrer::Engine.routes.url_helpers %>
2 | (function() {
3 | 'use strict';
4 |
5 | window.Referrer = function (params) {
6 | var _options = {
7 | cookies: {
8 | prefix: 'referrer',
9 | domain: null,
10 | path: '/'
11 | },
12 | user: {
13 | methods: {create: {url: '<%= urls.users_path %>'}}
14 | },
15 | session: {
16 | methods: {create: {url: '<%= urls.sessions_path %>'}}
17 | },
18 | source: {
19 | methods: {massCreate: {url: '<%= urls.mass_create_sources_path %>'}}
20 | },
21 | object: {name: 'referrer'},
22 | callback: null,
23 | csrfTokenFunction: function () {<%= Referrer.js_csrf_token %>},
24 | version: 1.0
25 | };
26 |
27 | var _storageNames, _urlSupport, _storageManager, _requestResolver;
28 |
29 | var UrlSupport = function () {
30 | this.getHostname = function (url) {
31 | var tmp = document.createElement('a');
32 | tmp.href = url;
33 | return tmp.hostname;
34 | };
35 | this.referrerUrl = document.referrer;
36 | this.referrerHostname = this.referrerUrl ? this.getHostname(this.referrerUrl) : null;
37 | this.locationUrl = window.location.toString();
38 | this.locationHostname = this.getHostname(this.locationUrl);
39 | };
40 |
41 | var StorageManager = function () {
42 | var _domain = _options.cookies.domain ||
43 | (_urlSupport.locationHostname == 'localhost' ? null : _urlSupport.locationHostname);
44 |
45 | this.setCookie = function (name, value, permanent, seconds) {
46 | var expires;
47 | var cookie = name + "=" + value + ';path=' + _options.cookies.path;
48 | if (_domain) {cookie = cookie + ';domain=' + _domain;}
49 | if (permanent) {
50 | expires = new Date();
51 | expires.setFullYear(expires.getFullYear() + 20);
52 | } else {
53 | if (seconds && typeof seconds == 'number') {
54 | expires = new Date();
55 | expires.setTime(expires.getTime() + seconds*1000);
56 | }
57 | }
58 | if (expires) {
59 | cookie = cookie + ';expires=' + expires.toUTCString();
60 | }
61 | document.cookie = cookie;
62 | };
63 |
64 | this.getCookie = function (name) {
65 | var pName = name + '=';
66 | var arr = document.cookie.split(';');
67 | for(var i=0; i), finished: false};
363 | });
364 | })();
--------------------------------------------------------------------------------