What is Segment?
Segment is a platform that lets you aggregate all your analytics data from your website and then send it to hundreds of other tools and platforms – such as Google Analytics, Mixpanel, Intercom, MailChimp, Facebook Pixel and even Amazon S3. With Segment you can track users’ activity throughout your application for many tools and platforms. Segment also provides libraries for integration into many languages. For Ruby, they have a gem called Analytics-Ruby.
We will be using Analytics-Ruby to identify and track users within Solidus, an open source Ruby On Rails eCommerce engine. What we wanted to archive with Segment is simple:
- Identify and track users and events
- Try not to impact the user’s experience – e.g. API errors or slow speeds when API calls are made.
The docs are very easy to follow. Within minutes I was up and running and sending data to segment.
# config/initializers/analytics_ruby.rb Analytics = Segment::Analytics.new({ write_key: ENV['SEGMENT_KEY'], on_error: Proc.new { |status, msg| print msg } })
The two examples below show options that we are going to use to send data to Segment.
# Identify the user for the people section Analytics.identify( { user_id: user.id, traits: { email: user.email, first_name: user.first_name, last_name: user.last_name } } )
Identify will simply allow us to tie an action to a user as well as to send some information about that user as traits.
# Track a user event Analytics.track( { user_id: user.id, event: 'Created Account' } )
Track is how we are going to record any action the user makes.
At this point we can simply copy and paste the two scripts above whenever we want to track an event or identify a user. We won’t do that, however, as it will go against the DRY principle and become a nightmare to maintain. So we will create a service for it, something like Track::Property.action.
This way we can call it from wherever we need to. Additionally if we ever need to change it or add a trait to a user, it can all be in one place.
While testing the events on segment, we had an error and, on production, it would have interrupted the users experience with a 500. As we don’t want errors and service disruptions from the analytics to impact the user, we can run a background job separate from the users process and have Bugsnag notify us if something goes wrong.
# app/jobs/identify_user_job.rb class IdentifyUserJob < ActiveJob::Base queue_as :default def perform(options) Analytics.identify(options) end end
When enqueueing IdentifyUserJob we will pass a hash as options to it, which will then be passed to Segment’s Analytics-Ruby.
# app/jobs/track_event_job.rb class TrackEventJob < ActiveJob::Base queue_as :default def perform(options) Analytics.track(options) end end
We will do the same for tracking events.
Tracking
Browsing products, ordering, promotions, coupons, sharing, etc… Segments offers a lot of E-commerce event tracking. I’m only going to cover a few in this post. However, I recommend that, to get an excellent understanding of what users are doing in your system, you track as many events as you can. With services such as Mixpanel you will begin to better understand your users’ behaviour and journey through your site.
Users
# app/services/track/user.rb class Track::User def initialize(user) @user = user end def logged_in_event(event, properties = {}) identify_user TrackEventJob.perform_later( { user_id: @user.id, event: event, properties: properties } ) end def logged_in identify_user TrackEventJob.perform_later( { user_id: @user.id, event: 'Sign In User' } ) end def identify_user IdentifyUserJob.perform_later( { user_id: @user.id, traits: { email: @user.email, firstName: @user.first_name, lastName: @user.last_name, logins: @user.sign_in_count, country: @user.country, } } ) end end
So our track user service looks something like above. We can now call Track::User.new(user).logged_in_event
and it will identify and track any event we specify in the params.
The Track::User.new(user).logged_in
will identify and track that the user has logged in. This will be fired only when users first log in. So how can we go about setting it? Well Solidus uses Devise which is based on warden. With warden, we can set a callback in the config for after the user has logged in.
# config/initializers/devise.rb Warden::Manager.after_set_user except: :fetch do |user, auth, opts| Track::User.new(user).logged_in end
Orders
The Segment Core ordering spec lists a lot of things we can track. But this is an overview, so we will only go over completed orders. The other specs will help you answer specific questions such as what step are people dropping of at. That way you can optimise your checkout process. More on this another time.
class Track::Order def initialize(order) @order = order end def completed TrackEventJob.perform_later({ user_id: @order.user_id || @order.email, event: 'Order Completed', properties: { orderId: @order.number, total: @order.total, revenue: @order.payment_total, shipping: @order.shipment_total, tax: @order.included_tax_total, discount: @order.adjustment_total.abs, currency: @order.currency, products: order_line_items } }) end def order_line_items line_items = [] @order.line_items.each do |line_item| line_items << { id: line_item.variant.id, sku: line_item.variant.sku, name: line_item.variant.name, price: line_item.price, quantity: line_item.quantity } end line_items end end
Create a deface for Solidus’ frontend orders shows view (solidus/frontend/app/views/spree/orders/show.html.erb) and update the order_just_completed()
conditional so it looks like the following.
<% if order_just_completed?(@order) %> <% ::Track::Order.new(@order).completed %> <strong><%= Spree.t(:thank_you_for_your_order) %></strong> <% end %>
Now when orders are completed, we will get all the line items and details about the order – including the user who placed it.
Products
class Track::Product def initialize(product) @product = product end def viewed TrackEventJob.perform_later({ user_id: current_spree_user, event: 'Product Viewed', properties: { id: @product.id, sku: @product.sku, name: @product.name, price: @product.price } } ) end end
We can track when a user searches for a product, views a product list or category, and when the list is filtered to find their ideal product. But we kept it simple for now, tracking only when a user views a product.
class Track::Subscriptions def initialize(subscription) @subscription = subscription end def subscribe TrackEventJob.perform_later( { user_id: @subscription.user.id, event: 'Subscribed User', properties: { plan: @subscription.product.name } } ) end def pause TrackEventJob.perform_later( { user_id: @subscription.user.id, event: 'Subscription Paused', properties: { plan: @subscription.product.name, } } ) end def cancel TrackEventJob.perform_later( { user_id: @subscription.user.id, event: 'Subscription Cancelled', properties: { plan: @subscription.product.name } } ) end def resume TrackEventJob.perform_later( { user_id: @subscription.user.id, event: 'Subscription Resume', properties: { plan: @subscription.product.name } } ) end end
Conclusion
With just a few simple steps, we have a reliable system in place to track the usage of our application and give us in-depth insights. Within our application, we are now tracking when users log in, when products are added to cart, when a product is viewed and when an order is placed. You can use the data from those events to make actionable changes to your store. Obviously, we can go much deeper. This was just a taster into Segment.
Before you go crazy adding events everywhere though, make sure that the data you receive will be useful. When it comes down to it, a tool like Segment is just as important as any other tool in your app stack. In the follow up post to this one, we will go over where to put all this data, make sense of all the data and how to make it work for you.
More Reading
- The Segment Spec outlines the semantic definition of the customer data that segment captures across all of the libraries and APIs.
- How Thoughtbot uses it with ruby.
- Maybe you want to add your own tool or platform into Segment, this document outlines what you need to do.