# encoding: utf-8 module Mongoid #:nodoc: module Tracking #:nodoc: # Include this module to add analytics tracking into a +root level+ document. # Use "track :field" to add a field named :field and an associated mongoid # field named after :field def self.included(base) base.class_eval do raise Errors::NotMongoid, "Must be included in a Mongoid::Document" unless self.ancestors.include? Mongoid::Document include Aggregates extend ClassMethods class_inheritable_accessor :tracked_fields self.tracked_fields = [] delegate :tracked_fields, :internal_track_name, :to => "self.class" end end module ClassMethods # Adds analytics tracking for +name+. Adds a +'name'_data+ mongoid # field as a Hash for tracking this information. Additionaly, hiddes # the field, so that the user can not mangle with the original one. # This is necessary so that Mongoid does not "dirty" the field # potentially overwriting the original data. def track(name) set_tracking_field(name.to_sym) create_tracking_accessors(name.to_sym) update_aggregates(name.to_sym) if aggregated? end protected # Returns the internal representation of the tracked field name def internal_track_name(name) "#{name}_data".to_sym end # Configures the internal fields for tracking. Additionally also creates # an index for the internal tracking field. def set_tracking_field(name) field internal_track_name(name), :type => Hash # , :default => {} # DONT make an index for this field. MongoDB indexes have limited # size and seems that this is not a good target for indexing. # index internal_track_name(name) tracked_fields << name end # Creates the tracking field accessor and also disables the original # ones from Mongoid. Hidding here the original accessors for the # Mongoid fields ensures they doesn't get dirty, so Mongoid does not # overwrite old data. def create_tracking_accessors(name) define_method(name) do |*aggr| Tracker.new(self, name, aggr) end # Should we just "undef" this methods? # They override the ones defined from Mongoid define_method("#{name}_data") do raise NoMethodError end define_method("#{name}_data=") do |m| raise NoMethodError end # I think it's important to override also the #{name}_changed? so # as to be sure Mongoid never mark this field as dirty. define_method("#{name}_changed?") do false end end # Updates the aggregated class for it to include a new tracking field def update_aggregates(name) aggregate_klass.track name end end end end