SessionLogger
The session logger is simply a observer that automatically writes data from the session to models with matching columns.
What it is good for
Session Logger is great for recording session based activites you want to group together.
Questions session logger can help answer:
-
How many users came in from a given ad campaign?
-
How many invites did those users from that campaign send?
-
How many purchases came when we directed people to this landing page?
-
Pretty much anything about users activity over a single session…
Setup
First install the gem:
gem install session_logger
And/or add it to your Gemfile
gem session_logger
Then create the initializer:
rails generate session_logger:initializer
Finally add ‘enable_session_logger’ to Application controller (or any controller you want logging to occur)
ApplicationController < ActionController::Base
enable_session_logging
end
Usage
1 Create metadata columns on the models you want metadata recorded on
add_column :users, :sl_campaign, :string
add_column :users, :sl_source, :string
add_column :users, :sl_medium, :string
add_column :users, :sl_term, :string
add_column :users, :sl_content, :string
add_column :users, :sl_landing_page, :string
add_column :users, :sl_session_id, :string
2 Be sure to prefix metadata columns with the model prefix (“sl_” by default)
SessionLogger.configure do |config|
config.model_prefix = "sl_" # means columns are like: sl_XXXX
end
3 Simply set that same prefixed name in the session and that metadata will be stored anytime a observed record is created during that session.
ApplicationController < ActionController::Base
enable_session_logging
before_filter :pull_campaign_data
def pull_campaign_data
first_time = session['meta_stored'].blank?
mapping = {"utm_source" => "sl_source", "utm_medium" => "sl_medium",
"utm_term" => "sl_term", "utm_content" => "sl_content",
"utm_campaign" =>"sl_campaign"}
new_values = {}
mapping.each do |param_key, session_key|
value = params[param_key]
if value.present? && session[session_key] != value
new_values[session_key] = value
end
end
#Override all the campaign values when new ones come in
#new values will be empty when link doesn't have utm params
if (new_values.present? || first_time)
#Store the landingpage with the utm params, and session id
new_values["sl_landing_uri"] = request.env["REQUEST_URI"]
new_values["sl_session_id"] = request.session_options[:id]
session.merge!(new_values)
end
session['meta_stored'] = true
end
end
4 Later segment your data naturally using the columns you defined
User.where(:sl_campaign => "facebook_ads_1").count
...
How it works
We create a new observer type that has access to both the controller and the model level call backs that will write metadata found in the session to models at creation time.
When a session key is found that matches a prefixed column for the model that is being created we simply append that data before save to the model effectively appending the metadata for that users session.
Gotchas
If you start monitoring a ton of metadata around the user you have to switch to db sessions instead of cookie store.
Don’t clear your whole session haphazardly. Thats where the metadata is stored
Choice to use columns instead of polymorphic table
One of the primary conerns we had when we were developing this gem was the trade offs between adding additional namespaced columns on the records and creating a seperate polymorphic join table for the logging information.
At the end of the day we choose to implement the logging solution with namespaced columns. Here are the pros and cons.
Columns solution:
Pros:
-
Speed and simplicity in creation of reports.
-
Speed of writes of those rows. (one write call vs two)
-
Customization of metadata per model without Null columns
Cons:
-
Pain to add so many repeated columns to potentially many tables
-
(OK) Slow down in read of individual rows (can be mitigated with scoped select statements)
-
(OK) Metadata doesn’t have its own timestamp (not really applicable as it should be the same as the write time of the original record)
The only real con that we couldn’t work around is the initial number of columns you will need to add to all your models. Many of them repeat in practice and this will give you a bad taste in your mouth but it seems like a better solution overall.
Optimizations
As a performance optimization you can reduce the list of models to observe in the initializer:
config.session_logger.logged_models = [MODEL_CONSTANTS,...]
License
This project rocks and uses MIT-LICENSE.