sojourn 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +14 -0
 - data/Gemfile +4 -0
 - data/README.md +118 -0
 - data/Rakefile +1 -0
 - data/lib/generators/sojourn/install_generator.rb +35 -0
 - data/lib/generators/sojourn/templates/config_initializer.rb +37 -0
 - data/lib/generators/sojourn/templates/create_sojourn_browsers.rb +13 -0
 - data/lib/generators/sojourn/templates/create_sojourn_campaigns.rb +10 -0
 - data/lib/generators/sojourn/templates/create_sojourn_events.rb +17 -0
 - data/lib/generators/sojourn/templates/create_sojourn_requests.rb +19 -0
 - data/lib/sojourn.rb +24 -0
 - data/lib/sojourn/browser.rb +27 -0
 - data/lib/sojourn/campaign.rb +27 -0
 - data/lib/sojourn/configuration.rb +28 -0
 - data/lib/sojourn/controller.rb +26 -0
 - data/lib/sojourn/event.rb +22 -0
 - data/lib/sojourn/request.rb +42 -0
 - data/lib/sojourn/serializers/symbol.rb +13 -0
 - data/lib/sojourn/session_stores/cookie.rb +49 -0
 - data/lib/sojourn/tracker.rb +76 -0
 - data/lib/sojourn/version.rb +3 -0
 - data/sojourn.gemspec +28 -0
 - metadata +125 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 05e30ba3490b5cb18a66e3ecf4bdb0048e0ce529
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 055a2accda2519d306ed020f9df334694cea68c0
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 8c8eaa338c482230664097aae7910ebae14f9ebace498dc9feff819d96fce1b66e65546c1416f3e23e3dee6342c0936fe209e081eb11a1a0aeff62de82a74947
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 70f60a8e7ba6a81055acd0306736b4feff026eed0f2dbac10d18510bd2e6e4dec6711a9c96eacac5eef6767ef12e1ce2b042d0739f31285ec95bdb9286f25f24
         
     | 
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,118 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Sojourn
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Simple source & event tracking for Rails. This gem automatically tracks *sojourners*
         
     | 
| 
      
 4 
     | 
    
         
            +
            (i.e. unique visitors) based on:
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            * Referer
         
     | 
| 
      
 7 
     | 
    
         
            +
            * UTM parameters
         
     | 
| 
      
 8 
     | 
    
         
            +
            * Browser (User Agent)
         
     | 
| 
      
 9 
     | 
    
         
            +
            * The currently logged-in user (i.e. `current_user`)
         
     | 
| 
      
 10 
     | 
    
         
            +
            * Various other request data
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ## How It Works
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            **Everything is tracked in the form of events.** Yep, events all the way down.
         
     | 
| 
      
 15 
     | 
    
         
            +
            (See 'Why Events?' below for the reasoning behind this.)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            Sojourn assigns each *sojourner* a UUID, which is tracked across requests. All events are
         
     | 
| 
      
 18 
     | 
    
         
            +
            associated with this UUID and with the current user's ID (if logged-in).
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            Events (`Sojourn::Event`) consist of a name, a set of properties (key-value hash) and information
         
     | 
| 
      
 21 
     | 
    
         
            +
            about the request. In the current ActiveRecord implementation, requests (`Sojourn::Request`) can
         
     | 
| 
      
 22 
     | 
    
         
            +
            be queried separately and may have many events. Requests also track browser (`Sojourn::Browser`)
         
     | 
| 
      
 23 
     | 
    
         
            +
            and campaign (`Sojourn::Campaign`) info as unique models. See 'Usage' below for the details
         
     | 
| 
      
 24 
     | 
    
         
            +
            of these models.
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 30 
     | 
    
         
            +
            # Track a custom event (highly encouraged!):
         
     | 
| 
      
 31 
     | 
    
         
            +
            sojourn.track! 'clicked call-to-action', plan_choice: 'enterprise'
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            e = Sojourn::Event.last
         
     | 
| 
      
 34 
     | 
    
         
            +
            e.name               # event name (e.g. 'clicked call-to-action')
         
     | 
| 
      
 35 
     | 
    
         
            +
            e.sojourner_uuid     # uuid tracked across requests, stored in cookie
         
     | 
| 
      
 36 
     | 
    
         
            +
            e.user               # User or nil
         
     | 
| 
      
 37 
     | 
    
         
            +
            e.properties         # key-value hash (e.g. "{ plan_choice: 'enterprise' }")
         
     | 
| 
      
 38 
     | 
    
         
            +
            e.request            # Sojourn::Request object
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            r = Sojourn::Request.last
         
     | 
| 
      
 41 
     | 
    
         
            +
            r.referer
         
     | 
| 
      
 42 
     | 
    
         
            +
            r.host
         
     | 
| 
      
 43 
     | 
    
         
            +
            r.path
         
     | 
| 
      
 44 
     | 
    
         
            +
            r.controller
         
     | 
| 
      
 45 
     | 
    
         
            +
            r.action
         
     | 
| 
      
 46 
     | 
    
         
            +
            r.params
         
     | 
| 
      
 47 
     | 
    
         
            +
            r.method
         
     | 
| 
      
 48 
     | 
    
         
            +
            r.ip_address
         
     | 
| 
      
 49 
     | 
    
         
            +
            r.campaign           # Sojourn::Campaign object (nil if no campaign detected)
         
     | 
| 
      
 50 
     | 
    
         
            +
            r.browser            # Sojourn::Browser object
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            b = Sojourn::Browser.last
         
     | 
| 
      
 53 
     | 
    
         
            +
            b.known?             # whether or not the browser was detected successfully
         
     | 
| 
      
 54 
     | 
    
         
            +
            b.user_agent
         
     | 
| 
      
 55 
     | 
    
         
            +
            b.name
         
     | 
| 
      
 56 
     | 
    
         
            +
            b.version
         
     | 
| 
      
 57 
     | 
    
         
            +
            b.platform
         
     | 
| 
      
 58 
     | 
    
         
            +
            b.bot?
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            c = Sojourn::Campaign.last
         
     | 
| 
      
 61 
     | 
    
         
            +
            c.path               # Base path (e.g. '/posts/2')
         
     | 
| 
      
 62 
     | 
    
         
            +
            c.params             # Notable (tracked) params, sorted. (Typically utm-style, but configurable.)
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
            ```
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            ## Default Events
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            The three built-in events are as follows:
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 71 
     | 
    
         
            +
            '!sojourning' # The sojourner has arrived from an external source.
         
     | 
| 
      
 72 
     | 
    
         
            +
            '!logged_in'  # The sojourner has logged-in.
         
     | 
| 
      
 73 
     | 
    
         
            +
            '!logged_out' # The sojourner has logged-out.
         
     | 
| 
      
 74 
     | 
    
         
            +
            ```
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            A `'!sojourning'` event takes place whenever any of the following is true:
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
            * The sojourner has never been seen before (i.e. direct traffic of some kind)
         
     | 
| 
      
 79 
     | 
    
         
            +
            * The referer is from an external source (i.e. not the current `request.host`)
         
     | 
| 
      
 80 
     | 
    
         
            +
            * The request contains tracked (utm-style) parameters. (These can be configured in the `sojourn.rb`
         
     | 
| 
      
 81 
     | 
    
         
            +
              initializer.)
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
            ## Installation
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
            Add this line to your application's Gemfile:
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 89 
     | 
    
         
            +
            gem 'sojourn'
         
     | 
| 
      
 90 
     | 
    
         
            +
            ```
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
            And then execute:
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                $ bundle
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
            To install migrations and the `sojourn.rb` initializer, execute:
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                $ rails g sojourn:install
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            ## Why Events? Why not track visits/visitors as their own objects?
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
            The idea is that, at a certain scale, this kind of tracking should be dumped directly into
         
     | 
| 
      
 103 
     | 
    
         
            +
            append-only logs (or an event bus / messaging queue) for asynchronous processing.
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
            This is made easier when everything can be represented, at a basic level, as discrete events.
         
     | 
| 
      
 106 
     | 
    
         
            +
            In theory, it works with just about any data store, and makes for easy time series and funnel
         
     | 
| 
      
 107 
     | 
    
         
            +
            analysis. I'd like to move away from ActiveRecord at some point and open up the door for other,
         
     | 
| 
      
 108 
     | 
    
         
            +
            highly scalable data backends.
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
            ## Current Limitations (i.e. the 'todo' list)
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            * Tested only on rails 3.2.18 and ruby 2.0.0 with ActiveRecord and PostgreSQL.
         
     | 
| 
      
 113 
     | 
    
         
            +
            * Assumes `User` and `current_user` convention for user tracking.
         
     | 
| 
      
 114 
     | 
    
         
            +
            * Assumes that if `request.referer` does not match `request.host`, the referer is external to your
         
     | 
| 
      
 115 
     | 
    
         
            +
              website.
         
     | 
| 
      
 116 
     | 
    
         
            +
            * Relies solely on cookies to track visitor UUID across requests (no JS, fingerprinting, etc)
         
     | 
| 
      
 117 
     | 
    
         
            +
            * Relies on ActiveRecord for storage. (At a bigger scale, append-only logs are preferred)
         
     | 
| 
      
 118 
     | 
    
         
            +
            * There are no tests.
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'bundler/gem_tasks'
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rails/generators/active_record'
         
     | 
| 
      
 2 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 3 
     | 
    
         
            +
              module Generators
         
     | 
| 
      
 4 
     | 
    
         
            +
                class InstallGenerator < Rails::Generators::Base
         
     | 
| 
      
 5 
     | 
    
         
            +
                  include Rails::Generators::Migration
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  desc 'Copies sojourn migrations to your file.'
         
     | 
| 
      
 8 
     | 
    
         
            +
                  source_root File.expand_path('../templates', __FILE__)
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def self.next_migration_number(dirname)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    next_migration_number = current_migration_number(dirname) + 1
         
     | 
| 
      
 12 
     | 
    
         
            +
                    if ::ActiveRecord::Base.timestamped_migrations
         
     | 
| 
      
 13 
     | 
    
         
            +
                      [Time.now.utc.strftime('%Y%m%d%H%M%S'), format('%.14d', next_migration_number)].max
         
     | 
| 
      
 14 
     | 
    
         
            +
                    else
         
     | 
| 
      
 15 
     | 
    
         
            +
                      format('%.3d', next_migration_number)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def create_config_file
         
     | 
| 
      
 20 
     | 
    
         
            +
                    template 'config_initializer.rb', 'config/initializers/sojourn.rb'
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def create_migrations
         
     | 
| 
      
 24 
     | 
    
         
            +
                    %w(campaigns browsers requests events).map { |m| "create_sojourn_#{m}" }.each do |name|
         
     | 
| 
      
 25 
     | 
    
         
            +
                      if self.class.migration_exists?('db/migrate', name)
         
     | 
| 
      
 26 
     | 
    
         
            +
                        say "        #{set_color('skip', :yellow)}  #{name}.rb (migration already exists)"
         
     | 
| 
      
 27 
     | 
    
         
            +
                      else
         
     | 
| 
      
 28 
     | 
    
         
            +
                        migration_template "#{name}.rb", "db/migrate/#{name}.rb"
         
     | 
| 
      
 29 
     | 
    
         
            +
                        sleep 1
         
     | 
| 
      
 30 
     | 
    
         
            +
                      end
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Sojourn.configure do |config|
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              # A new '!sojourning' event is created whenever:
         
     | 
| 
      
 4 
     | 
    
         
            +
              #    1. The 'referer' is external
         
     | 
| 
      
 5 
     | 
    
         
            +
              #    2. There are utm-style parameters in the request.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #    3. The visitor is new and has never been assigned a 'sojourner_id' (i.e. direct traffic)
         
     | 
| 
      
 7 
     | 
    
         
            +
              # The two events '!logged_in' and '!logged_out' are created whenever sojourn detects a
         
     | 
| 
      
 8 
     | 
    
         
            +
              # change to `current_user`.
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              # To disable all automatic event tracking, uncomment the following line:
         
     | 
| 
      
 11 
     | 
    
         
            +
              # config.tracking_enabled = false
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              # You may customize the list of tracked (utm-style) parameters here.
         
     | 
| 
      
 14 
     | 
    
         
            +
              # Note: Using tracked params on internal links within your site is NOT recommended, as this will
         
     | 
| 
      
 15 
     | 
    
         
            +
              #       result in many repeated '!sojourning' events. Instead, you should use a different set
         
     | 
| 
      
 16 
     | 
    
         
            +
              #       of parameters (such as 'from', 'source', or 'referer') and NOT include them here.
         
     | 
| 
      
 17 
     | 
    
         
            +
              # Default: [:utm_source, :utm_medium, :utm_term, :utm_content, :utm_campaign]
         
     | 
| 
      
 18 
     | 
    
         
            +
              #
         
     | 
| 
      
 19 
     | 
    
         
            +
              # config.campaign_params += [:my_custom_tracking_param]
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              # By default, sojourn will attach `user_id`, `sojourner_uuid`, and a `created_at` timestamp
         
     | 
| 
      
 22 
     | 
    
         
            +
              # to every event. If you would like to add additional properties, you may do so as follows.
         
     | 
| 
      
 23 
     | 
    
         
            +
              # Note: This block will be called in the context of your controller.
         
     | 
| 
      
 24 
     | 
    
         
            +
              #
         
     | 
| 
      
 25 
     | 
    
         
            +
              # config.default_properties do |p|
         
     | 
| 
      
 26 
     | 
    
         
            +
              #   p[:rails_env] = Rails.env
         
     | 
| 
      
 27 
     | 
    
         
            +
              #   p[:my_custom_property] = method_defined_in_controller
         
     | 
| 
      
 28 
     | 
    
         
            +
              # end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              # By default, sojourn uses a signed, permanent cookie to store the sojourner uuid. You
         
     | 
| 
      
 31 
     | 
    
         
            +
              # may specifiy an alternate/custom session_store, or change the name of the cookie that
         
     | 
| 
      
 32 
     | 
    
         
            +
              # gets created. (default is `:_sojourn`)
         
     | 
| 
      
 33 
     | 
    
         
            +
              #
         
     | 
| 
      
 34 
     | 
    
         
            +
              # config.session_store = Sojourn::SessionStores::Cookie
         
     | 
| 
      
 35 
     | 
    
         
            +
              # config.cookie_name = :my_custom_cookie_name
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class CreateSojournBrowsers < ActiveRecord::Migration
         
     | 
| 
      
 2 
     | 
    
         
            +
              def change
         
     | 
| 
      
 3 
     | 
    
         
            +
                create_table :sojourn_browsers do |t|
         
     | 
| 
      
 4 
     | 
    
         
            +
                  t.text :user_agent, unique: true
         
     | 
| 
      
 5 
     | 
    
         
            +
                  t.string :name
         
     | 
| 
      
 6 
     | 
    
         
            +
                  t.string :version
         
     | 
| 
      
 7 
     | 
    
         
            +
                  t.string :platform
         
     | 
| 
      
 8 
     | 
    
         
            +
                  t.boolean :known
         
     | 
| 
      
 9 
     | 
    
         
            +
                  t.boolean :bot
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
                add_index :sojourn_browsers, [:user_agent]
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class CreateSojournCampaigns < ActiveRecord::Migration
         
     | 
| 
      
 2 
     | 
    
         
            +
              def change
         
     | 
| 
      
 3 
     | 
    
         
            +
                create_table :sojourn_campaigns do |t|
         
     | 
| 
      
 4 
     | 
    
         
            +
                  t.string :path, limit: 2048
         
     | 
| 
      
 5 
     | 
    
         
            +
                  t.string :params, limit: 2048
         
     | 
| 
      
 6 
     | 
    
         
            +
                  t.timestamp :created_at
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
                add_index :sojourn_campaigns, [:path, :params]
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class CreateSojournEvents < ActiveRecord::Migration
         
     | 
| 
      
 2 
     | 
    
         
            +
              def change
         
     | 
| 
      
 3 
     | 
    
         
            +
                create_table :sojourn_events do |t|
         
     | 
| 
      
 4 
     | 
    
         
            +
                  t.string :sojourner_uuid, limit: 36, null: false
         
     | 
| 
      
 5 
     | 
    
         
            +
                  t.string :name
         
     | 
| 
      
 6 
     | 
    
         
            +
                  t.text :properties
         
     | 
| 
      
 7 
     | 
    
         
            +
                  t.references :sojourn_request
         
     | 
| 
      
 8 
     | 
    
         
            +
                  t.references :user
         
     | 
| 
      
 9 
     | 
    
         
            +
                  t.timestamp :created_at
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
                add_index :sojourn_events, [:sojourner_uuid]
         
     | 
| 
      
 12 
     | 
    
         
            +
                add_index :sojourn_events, [:sojourn_request_id]
         
     | 
| 
      
 13 
     | 
    
         
            +
                add_index :sojourn_events, [:user_id]
         
     | 
| 
      
 14 
     | 
    
         
            +
                add_index :sojourn_events, [:created_at]
         
     | 
| 
      
 15 
     | 
    
         
            +
                add_index :sojourn_events, [:name]
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class CreateSojournRequests < ActiveRecord::Migration
         
     | 
| 
      
 2 
     | 
    
         
            +
              def change
         
     | 
| 
      
 3 
     | 
    
         
            +
                create_table :sojourn_requests do |t|
         
     | 
| 
      
 4 
     | 
    
         
            +
                  t.references :sojourn_campaign
         
     | 
| 
      
 5 
     | 
    
         
            +
                  t.references :sojourn_browser
         
     | 
| 
      
 6 
     | 
    
         
            +
                  t.string :host, limit: 2048
         
     | 
| 
      
 7 
     | 
    
         
            +
                  t.string :path, limit: 2048
         
     | 
| 
      
 8 
     | 
    
         
            +
                  t.string :method
         
     | 
| 
      
 9 
     | 
    
         
            +
                  t.string :controller
         
     | 
| 
      
 10 
     | 
    
         
            +
                  t.string :action
         
     | 
| 
      
 11 
     | 
    
         
            +
                  t.string :ip_address
         
     | 
| 
      
 12 
     | 
    
         
            +
                  t.text :params
         
     | 
| 
      
 13 
     | 
    
         
            +
                  t.text :referer
         
     | 
| 
      
 14 
     | 
    
         
            +
                  t.timestamp :created_at
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
                add_index :sojourn_requests, [:sojourn_campaign_id]
         
     | 
| 
      
 17 
     | 
    
         
            +
                add_index :sojourn_requests, [:sojourn_browser_id]
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/sojourn.rb
    ADDED
    
    | 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'sojourn/version'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'sojourn/configuration'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'sojourn/controller'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 6 
     | 
    
         
            +
              def self.table_name_prefix
         
     | 
| 
      
 7 
     | 
    
         
            +
                'sojourn_'
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def self.configure(&block)
         
     | 
| 
      
 11 
     | 
    
         
            +
                block.call(config)
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def self.config
         
     | 
| 
      
 15 
     | 
    
         
            +
                @config ||= Configuration.new
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              def self.tables_exist?
         
     | 
| 
      
 19 
     | 
    
         
            +
                @tables_exist ||= %w(sojourn_events sojourn_requests sojourn_browsers sojourn_campaigns)
         
     | 
| 
      
 20 
     | 
    
         
            +
                                  .map { |t| ActiveRecord::Base.connection.table_exists?(t) }.all?
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            ActionController::Base.send :include, Sojourn::Controller
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'browser'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Browser < ActiveRecord::Base
         
     | 
| 
      
 5 
     | 
    
         
            +
                has_many :requests, foreign_key: :sojourn_browser_id
         
     | 
| 
      
 6 
     | 
    
         
            +
                has_many :events, through: :requests
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def self.from_request(request)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  where(user_agent: request.user_agent).first_or_initialize
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                before_validation do
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self.name ||= browser.name
         
     | 
| 
      
 14 
     | 
    
         
            +
                  self.version ||= browser.version
         
     | 
| 
      
 15 
     | 
    
         
            +
                  self.platform ||= browser.platform
         
     | 
| 
      
 16 
     | 
    
         
            +
                  self.known ||= browser.known?
         
     | 
| 
      
 17 
     | 
    
         
            +
                  self.bot ||= browser.bot?
         
     | 
| 
      
 18 
     | 
    
         
            +
                  true # otherwise .valid? will return false
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              private
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def browser
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @browser ||= ::Browser.new(user_agent: user_agent)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Campaign < ActiveRecord::Base
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                has_many :requests, foreign_key: :sojourn_campaign_id
         
     | 
| 
      
 5 
     | 
    
         
            +
                has_many :events, through: :requests
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def from_request(request)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    return unless (params = tracked_params(request.params)).any?
         
     | 
| 
      
 11 
     | 
    
         
            +
                    where(path: request.path.downcase.try(:truncate, 2048))
         
     | 
| 
      
 12 
     | 
    
         
            +
                      .where(params: params.to_param.try(:truncate, 2048)).first_or_initialize
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                private
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def tracked_params(params)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    Hash[filter_params(params).sort.map { |k, v| [k.downcase, v.downcase] }]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def filter_params(params)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    params.slice(*Sojourn.config.campaign_params).delete_if { |_, v| v.blank? }
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'session_stores/cookie'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Configuration
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              attr_accessor :campaign_params
         
     | 
| 
      
 6 
     | 
    
         
            +
              attr_accessor :session_store
         
     | 
| 
      
 7 
     | 
    
         
            +
              attr_accessor :cookie_name
         
     | 
| 
      
 8 
     | 
    
         
            +
              attr_accessor :tracking_enabled
         
     | 
| 
      
 9 
     | 
    
         
            +
              attr_accessor :default_properties_block
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def initialize
         
     | 
| 
      
 12 
     | 
    
         
            +
                set_defaults
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def default_properties(&block)
         
     | 
| 
      
 16 
     | 
    
         
            +
                self.default_properties_block = block
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            private
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def set_defaults
         
     | 
| 
      
 22 
     | 
    
         
            +
                self.campaign_params = [:utm_source, :utm_medium, :utm_term, :utm_content, :utm_campaign]
         
     | 
| 
      
 23 
     | 
    
         
            +
                self.session_store = Sojourn::SessionStores::Cookie
         
     | 
| 
      
 24 
     | 
    
         
            +
                self.cookie_name = :_sojourn
         
     | 
| 
      
 25 
     | 
    
         
            +
                self.tracking_enabled = true
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'tracker'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Controller
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  base.before_filter :track_sojourning
         
     | 
| 
      
 8 
     | 
    
         
            +
                  base.before_filter :save_sojourn_session
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def sojourn
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @sojourn ||= Tracker.new(self)
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              private
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def track_sojourning
         
     | 
| 
      
 18 
     | 
    
         
            +
                  sojourn.sojourning!
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def save_sojourn_session
         
     | 
| 
      
 22 
     | 
    
         
            +
                  sojourn.update_session!
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Event < ActiveRecord::Base
         
     | 
| 
      
 3 
     | 
    
         
            +
                DEFAULT_FIELDS = [:id, :sojourner_uuid, :name, :properties, :sojourn_request_id, :user_id, :created_at]
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                belongs_to :request, foreign_key: :sojourn_request_id
         
     | 
| 
      
 6 
     | 
    
         
            +
                belongs_to :user
         
     | 
| 
      
 7 
     | 
    
         
            +
                has_one :campaign, through: :request
         
     | 
| 
      
 8 
     | 
    
         
            +
                has_one :browser, through: :request
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                serialize :properties
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                before_save do
         
     | 
| 
      
 13 
     | 
    
         
            +
                  properties.keys.map(&:to_sym).each do |key|
         
     | 
| 
      
 14 
     | 
    
         
            +
                    send("#{key}=", properties[key]) if self.class.available_fields.include?(key)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def self.available_fields
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @available_fields ||= column_names.map(&:to_sym) - DEFAULT_FIELDS
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'campaign'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'browser'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'serializers/symbol'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'addressable/uri'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Request < ActiveRecord::Base
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :user_agent
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                serialize :method, Serializers::Symbol
         
     | 
| 
      
 11 
     | 
    
         
            +
                serialize :params
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                belongs_to :campaign, foreign_key: :sojourn_campaign_id
         
     | 
| 
      
 14 
     | 
    
         
            +
                belongs_to :browser, foreign_key: :sojourn_browser_id
         
     | 
| 
      
 15 
     | 
    
         
            +
                has_many :events, foreign_key: :sojourn_request_id
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def self.from_request(request)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  new referer: request.referer.try(:truncate, 2048),
         
     | 
| 
      
 19 
     | 
    
         
            +
                      host: request.host.try(:truncate, 2048),
         
     | 
| 
      
 20 
     | 
    
         
            +
                      path: request.path.try(:truncate, 2048),
         
     | 
| 
      
 21 
     | 
    
         
            +
                      controller: request.params[:controller],
         
     | 
| 
      
 22 
     | 
    
         
            +
                      action: request.params[:action],
         
     | 
| 
      
 23 
     | 
    
         
            +
                      params: request.filtered_parameters.with_indifferent_access.except(:controller, :action),
         
     | 
| 
      
 24 
     | 
    
         
            +
                      method: request.request_method_symbol,
         
     | 
| 
      
 25 
     | 
    
         
            +
                      ip_address: request.remote_ip,
         
     | 
| 
      
 26 
     | 
    
         
            +
                      user_agent: request.user_agent
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                before_validation do
         
     | 
| 
      
 30 
     | 
    
         
            +
                  self.campaign ||= Campaign.from_request(self)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  self.browser ||= Browser.from_request(self) if user_agent
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def outside_referer?
         
     | 
| 
      
 35 
     | 
    
         
            +
                  referer.present? && Addressable::URI.parse(referer).host != host
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def any_utm_data?
         
     | 
| 
      
 39 
     | 
    
         
            +
                  Sojourn.config.campaign_params.map { |p| params[p].present? }.any?
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 2 
     | 
    
         
            +
              module SessionStores
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Cookie
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  def initialize(ctx)
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @ctx = ctx
         
     | 
| 
      
 7 
     | 
    
         
            +
                  end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def sojourner_uuid
         
     | 
| 
      
 10 
     | 
    
         
            +
                    cookie_data[:uuid]
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def sojourner_uuid=(value)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    cookies.permanent.signed[cookie_name] = { value: cookie_data.merge(uuid: value) }
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def sojourner_tracked?
         
     | 
| 
      
 18 
     | 
    
         
            +
                    cookie_data.key?(:uuid)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def user_id
         
     | 
| 
      
 22 
     | 
    
         
            +
                    cookie_data[:user_id]
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def user_id=(value)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    cookies.permanent.signed[cookie_name] = { value: cookie_data.merge(user_id: value) }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def user_tracked?
         
     | 
| 
      
 30 
     | 
    
         
            +
                    cookie_data.key?(:user_id)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                private
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def cookie_data
         
     | 
| 
      
 36 
     | 
    
         
            +
                    cookies.signed[cookie_name] || {}
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def cookies
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @ctx.send(:cookies)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def cookie_name
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @cookie_name ||= Sojourn.config.cookie_name
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,76 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'request'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'event'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Sojourn
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Tracker
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_accessor :ctx
         
     | 
| 
      
 7 
     | 
    
         
            +
                delegate :current_user, to: :ctx
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(ctx)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  self.ctx = ctx
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def track!(event_name, properties = {}, user_id = current_user_id)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  return unless Sojourn.tables_exist?
         
     | 
| 
      
 15 
     | 
    
         
            +
                  properties = default_event_properties.merge(properties)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  Event.create! sojourner_uuid: sojourner_uuid, name: event_name, request: request,
         
     | 
| 
      
 17 
     | 
    
         
            +
                                properties: properties, user_id: user_id
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def sojourning!
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return unless Sojourn.config.tracking_enabled && Sojourn.tables_exist?
         
     | 
| 
      
 22 
     | 
    
         
            +
                  track!('!sojourning') if sojourning?
         
     | 
| 
      
 23 
     | 
    
         
            +
                  track_user_change! if user_changed?
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def track_user_change!
         
     | 
| 
      
 27 
     | 
    
         
            +
                  return unless user_changed?
         
     | 
| 
      
 28 
     | 
    
         
            +
                  track!('!logged_out', {}, session.user_id) if session.user_id
         
     | 
| 
      
 29 
     | 
    
         
            +
                  track!('!logged_in', {}, current_user_id) if current_user_id
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def update_session!
         
     | 
| 
      
 33 
     | 
    
         
            +
                  session.sojourner_uuid ||= sojourner_uuid
         
     | 
| 
      
 34 
     | 
    
         
            +
                  session.user_id = current_user_id
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              private
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def request
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @request ||= Request.from_request(ctx.request)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def session
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @session ||= Sojourn.config.session_store.new(ctx)
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def sojourner_uuid
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @sojourner_uuid ||= session.sojourner_uuid || SecureRandom.uuid
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def sojourning?
         
     | 
| 
      
 52 
     | 
    
         
            +
                  request.outside_referer? || request.any_utm_data? || !session.sojourner_tracked?
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def user_changed?
         
     | 
| 
      
 56 
     | 
    
         
            +
                  session.user_tracked? && session.user_id != current_user_id
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def current_user_id
         
     | 
| 
      
 60 
     | 
    
         
            +
                  current_user.try(:id)
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def default_event_properties
         
     | 
| 
      
 64 
     | 
    
         
            +
                  @default_event_properties ||= fetch_default_properties
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def fetch_default_properties(properties = {})
         
     | 
| 
      
 68 
     | 
    
         
            +
                  if Sojourn.config.default_properties_block
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @ctx.define_singleton_method :sojourn_event_properties,
         
     | 
| 
      
 70 
     | 
    
         
            +
                                                 Sojourn.config.default_properties_block
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  @ctx.sojourn_event_properties(properties) if @ctx.respond_to? :sojourn_event_properties
         
     | 
| 
      
 73 
     | 
    
         
            +
                  properties
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
            end
         
     | 
    
        data/sojourn.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            lib = File.expand_path('../lib', __FILE__)
         
     | 
| 
      
 2 
     | 
    
         
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'sojourn/version'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Gem::Specification.new do |spec|
         
     | 
| 
      
 6 
     | 
    
         
            +
              spec.name          = 'sojourn'
         
     | 
| 
      
 7 
     | 
    
         
            +
              spec.version       = Sojourn::VERSION
         
     | 
| 
      
 8 
     | 
    
         
            +
              spec.authors       = ['Smudge']
         
     | 
| 
      
 9 
     | 
    
         
            +
              spec.email         = ['nathan@ngriffith.com']
         
     | 
| 
      
 10 
     | 
    
         
            +
              spec.summary       = 'Simple user source tracking for Rails.'
         
     | 
| 
      
 11 
     | 
    
         
            +
              spec.description   = %(
         
     | 
| 
      
 12 
     | 
    
         
            +
            Sojourn tracks site visitors and sources, with the ability to recognise
         
     | 
| 
      
 13 
     | 
    
         
            +
            multiple sources per visitor. Each time a new source is detected,
         
     | 
| 
      
 14 
     | 
    
         
            +
            sojourn tracks the referer, utm data, and logged-in user (if any)).gsub("\n", ' ')
         
     | 
| 
      
 15 
     | 
    
         
            +
              spec.homepage      = ''
         
     | 
| 
      
 16 
     | 
    
         
            +
              spec.license       = ''
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              spec.files         = `git ls-files -z`.split("\x0")
         
     | 
| 
      
 19 
     | 
    
         
            +
              spec.executables   = spec.files.grep(/^bin/) { |f| File.basename(f) }
         
     | 
| 
      
 20 
     | 
    
         
            +
              spec.test_files    = spec.files.grep(/^(test|spec|features)/)
         
     | 
| 
      
 21 
     | 
    
         
            +
              spec.require_paths = ['lib']
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              spec.add_development_dependency 'bundler', '~> 1.7'
         
     | 
| 
      
 24 
     | 
    
         
            +
              spec.add_development_dependency 'rake', '~> 10.0'
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              spec.add_dependency 'browser', '>= 0.8.0'
         
     | 
| 
      
 27 
     | 
    
         
            +
              spec.add_dependency 'addressable', '>= 2.3.1'
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,125 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: sojourn
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.7
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Smudge
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2015-03-02 00:00:00.000000000 Z
         
     | 
| 
      
 12 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 14 
     | 
    
         
            +
              name: bundler
         
     | 
| 
      
 15 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 16 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 17 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 18 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 19 
     | 
    
         
            +
                    version: '1.7'
         
     | 
| 
      
 20 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 21 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 22 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 23 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 24 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 26 
     | 
    
         
            +
                    version: '1.7'
         
     | 
| 
      
 27 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 28 
     | 
    
         
            +
              name: rake
         
     | 
| 
      
 29 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 30 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 31 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 33 
     | 
    
         
            +
                    version: '10.0'
         
     | 
| 
      
 34 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 35 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 36 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 37 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 40 
     | 
    
         
            +
                    version: '10.0'
         
     | 
| 
      
 41 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 42 
     | 
    
         
            +
              name: browser
         
     | 
| 
      
 43 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 44 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 45 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 46 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 47 
     | 
    
         
            +
                    version: 0.8.0
         
     | 
| 
      
 48 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 49 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 50 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 51 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 52 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 53 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 54 
     | 
    
         
            +
                    version: 0.8.0
         
     | 
| 
      
 55 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 56 
     | 
    
         
            +
              name: addressable
         
     | 
| 
      
 57 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 58 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 59 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 60 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 61 
     | 
    
         
            +
                    version: 2.3.1
         
     | 
| 
      
 62 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 63 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 64 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 66 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 67 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 68 
     | 
    
         
            +
                    version: 2.3.1
         
     | 
| 
      
 69 
     | 
    
         
            +
            description: " Sojourn tracks site visitors and sources, with the ability to recognise
         
     | 
| 
      
 70 
     | 
    
         
            +
              multiple sources per visitor. Each time a new source is detected, sojourn tracks
         
     | 
| 
      
 71 
     | 
    
         
            +
              the referer, utm data, and logged-in user (if any)"
         
     | 
| 
      
 72 
     | 
    
         
            +
            email:
         
     | 
| 
      
 73 
     | 
    
         
            +
            - nathan@ngriffith.com
         
     | 
| 
      
 74 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 75 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 76 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 77 
     | 
    
         
            +
            files:
         
     | 
| 
      
 78 
     | 
    
         
            +
            - ".gitignore"
         
     | 
| 
      
 79 
     | 
    
         
            +
            - Gemfile
         
     | 
| 
      
 80 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 81 
     | 
    
         
            +
            - Rakefile
         
     | 
| 
      
 82 
     | 
    
         
            +
            - lib/generators/sojourn/install_generator.rb
         
     | 
| 
      
 83 
     | 
    
         
            +
            - lib/generators/sojourn/templates/config_initializer.rb
         
     | 
| 
      
 84 
     | 
    
         
            +
            - lib/generators/sojourn/templates/create_sojourn_browsers.rb
         
     | 
| 
      
 85 
     | 
    
         
            +
            - lib/generators/sojourn/templates/create_sojourn_campaigns.rb
         
     | 
| 
      
 86 
     | 
    
         
            +
            - lib/generators/sojourn/templates/create_sojourn_events.rb
         
     | 
| 
      
 87 
     | 
    
         
            +
            - lib/generators/sojourn/templates/create_sojourn_requests.rb
         
     | 
| 
      
 88 
     | 
    
         
            +
            - lib/sojourn.rb
         
     | 
| 
      
 89 
     | 
    
         
            +
            - lib/sojourn/browser.rb
         
     | 
| 
      
 90 
     | 
    
         
            +
            - lib/sojourn/campaign.rb
         
     | 
| 
      
 91 
     | 
    
         
            +
            - lib/sojourn/configuration.rb
         
     | 
| 
      
 92 
     | 
    
         
            +
            - lib/sojourn/controller.rb
         
     | 
| 
      
 93 
     | 
    
         
            +
            - lib/sojourn/event.rb
         
     | 
| 
      
 94 
     | 
    
         
            +
            - lib/sojourn/request.rb
         
     | 
| 
      
 95 
     | 
    
         
            +
            - lib/sojourn/serializers/symbol.rb
         
     | 
| 
      
 96 
     | 
    
         
            +
            - lib/sojourn/session_stores/cookie.rb
         
     | 
| 
      
 97 
     | 
    
         
            +
            - lib/sojourn/tracker.rb
         
     | 
| 
      
 98 
     | 
    
         
            +
            - lib/sojourn/version.rb
         
     | 
| 
      
 99 
     | 
    
         
            +
            - sojourn.gemspec
         
     | 
| 
      
 100 
     | 
    
         
            +
            homepage: ''
         
     | 
| 
      
 101 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 102 
     | 
    
         
            +
            - ''
         
     | 
| 
      
 103 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 104 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 105 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 106 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 107 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 108 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 109 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 110 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 111 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 112 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 113 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 114 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 115 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 116 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 117 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 118 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 119 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 120 
     | 
    
         
            +
            rubygems_version: 2.2.2
         
     | 
| 
      
 121 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 122 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 123 
     | 
    
         
            +
            summary: Simple user source tracking for Rails.
         
     | 
| 
      
 124 
     | 
    
         
            +
            test_files: []
         
     | 
| 
      
 125 
     | 
    
         
            +
            has_rdoc: 
         
     |