module Mongoid::Tracking::Aggregates::ClassMethods

Public Instance Methods

aggregate(name, &block) click to toggle source
Defines an aggregate token to an already tracked model. It defines
a new mongoid model named after the original model.

Example:

<tt>class Page</tt>
<tt>  include Mongoid::Document</tt>
<tt>  include Mongoid::Tracking</tt>
<tt>  track :visits</tt>
<tt>  aggregate :browsers do |b|</tt>
<tt>    b.split(" ").first</tt>
<tt>  end</tt>
<tt>end</tt>

A new model is defined as <tt>class PageAggregates</tt>

This model has the following structure:

<tt>belongs_to :page</tt>
<tt>field :ns, :type => String</tt>
<tt>field :key, :type => String</tt>
<tt>index [:page_id, :ns, :key], :unique => true</tt>
<tt>track :[original_parent_tracking_data]</tt>
<tt>track :...</tt>

:ns is the "namespace". It's the name you put along the
"aggregate :browsers" in the original model definition.

:key is your aggregation key. This is the value you are required to
return in the "aggregate" block.

With the above structure, you can always query aggregates directly

 using Mongoid this way:

<tt>TestModelAggregates.where(:ns => "browsers", :key => "explorer").first</tt>

But you are encouraged to use Trackoid methods whenever possible.
# File lib/mongoid/tracking/aggregates.rb, line 60
def aggregate(name, &block)
  raise Errors::AggregationAlreadyDefined.new(self.name, name) if aggregate_fields.has_key? name
  raise Errors::AggregationNameDeprecated.new(name) if DEPRECATED_TOKENS.include? name.to_s

  define_aggregate_model if aggregate_klass.nil?
  has_many internal_accessor_name(name), class_name: aggregate_klass.to_s
  add_aggregate_field(name, block)
  create_aggregation_accessors(name)
end
aggregated?() click to toggle source

Return true if this model has aggregated data.

# File lib/mongoid/tracking/aggregates.rb, line 71
def aggregated?
  !aggregate_klass.nil?
end

Protected Instance Methods

add_aggregate_field(name, block) click to toggle source

Adds the aggregate field to the array of aggregated fields.

# File lib/mongoid/tracking/aggregates.rb, line 127
def add_aggregate_field(name, block)
  aggregate_fields[name] = block
end
create_aggregation_accessors(name) click to toggle source
# File lib/mongoid/tracking/aggregates.rb, line 144
def create_aggregation_accessors(name)
  # Aggregation accessors in the model acts like a named scopes
  define_method(name) do |*args|
    TrackerAggregates.new(self, name, args)
  end

  define_method("#{name}_with_track") do |track_field, *args|
    TrackerAggregates.new(self, name, args, track_field)
  end

  define_method("#{name}=") do
    raise NoMethodError
  end
end
define_aggregate_model() click to toggle source

Defines the aggregation model. It checks for class name conflicts

# File lib/mongoid/tracking/aggregates.rb, line 87
def define_aggregate_model
  unless defined?(Rails) && Rails.env.development?
    raise Errors::ClassAlreadyDefined.new(internal_aggregates_name) if foreign_class_defined?
  end

  parent = self
  
  define_klass do
    include Mongoid::Document
    include Mongoid::Tracking

    # Make the relation to the original class
    belongs_to parent.name.demodulize.underscore.to_sym, class_name: parent.name

    # Internal fields to track aggregation token and keys
    field :ns, type: String
    field :key, type: String

    index({ 
      parent.name.foreign_key.to_sym => 1,
      ns: 1,
      key: 1
    }, { unique: true, background: true })            

    # Include parent tracking data.
    parent.tracked_fields.each { |track_field| track track_field }
  end

  self.aggregate_klass = internal_aggregates_name.constantize
end
define_klass(&block) click to toggle source

Defines the aggregation external class. This class is named after the original class model but with “Aggregates” appended. Example: TestModel ==> TestModelAggregates

# File lib/mongoid/tracking/aggregates.rb, line 134
def define_klass(&block)
  scope = internal_aggregates_name.split('::')
  klass = scope.pop
  scope = scope.inject(Object) do |scope, const_name| 
    scope.const_get(const_name)
  end
  klass = scope.const_set(klass, Class.new)
  klass.class_eval(&block)
end
foreign_class_defined?() click to toggle source

Returns true if there is a class defined with the same name as our aggregate class.

# File lib/mongoid/tracking/aggregates.rb, line 120
def foreign_class_defined?
  internal_aggregates_name.constantize && true
rescue NameError
  false
end
internal_accessor_name(name) click to toggle source
# File lib/mongoid/tracking/aggregates.rb, line 82
def internal_accessor_name(name)
  (name.to_s + "_accessor").to_sym
end
internal_aggregates_name() click to toggle source

Returns the internal representation of the aggregates class name

# File lib/mongoid/tracking/aggregates.rb, line 77
def internal_aggregates_name
  str = self.to_s.underscore + "_aggregates"
  str.camelize
end