Via the creation of a YAML file specifying statistics to track, and an extension class to specify the way to find those statistics, Metrify provides an easy way to aggregate and display statistics over time. The minimal unit of aggregation is 1 day, and provided input times are set to midnight before calculations.
If Highcharts is used in your project, functionality is included to immediately use a default chart.
Install metrify:
$ gem install metrify
In your environment.rb file, add to Rails::Initializer.run do |config| block:
config.gem "metrify"
To add the ability for a class to generate aggregated statistics over time, perform the following steps:
-
Create .yml file defining metrics, and optional metric grouping filters
-
Include module functionality in class that will calculate metrics, and provide a method for each metric to be calulated.
You can create multiple classes that use this module. By default, the YML file loaded for the class will be on /config/(classname)_metrify.yml. Otherwise, specify the filename as an argument when calling “acts_as_metrify” within the class (shown below). RAILS_ROOT will be prepended.
Example YML file:
stats: element_a_count: display_name: Element A Count value_type: currency precision: 2 show_variance: true element_b_count: display_name: Element B Count element_1_count: display_name: Element 1 Count element_cat_count: display_name: Cat Count element_dog_count: display_name: Dog Count stat_order: [element_a_count, element_b_count, element_1_count, element_dog_count, element_cat_count] filters: type: numbers: set: [element_1_count] description: 'Numbers' letters: set: [element_a_count, element_b_count] description: 'Letts' animals: set: [element_cat_count, element_dog_count] description: 'Animals' furriness: furry: set: [element_cat_count, element_dog_count] description: 'Furry' not_furry: set: [element_1_count, element_a_count, element_b_count, element_c_count] description: 'Not Furry'
Example Class, implementing calculation methods for metrics defined in YML file:
class MetricsClass < ActiveRecord::Base include Metrify acts_as_metrify class << self def element_a_count(start_date, end_time) # Code calculating number (probably a SQL query using start and end dates) end ...(more methods, one for each defined metric) end end
The class will need the minimal columns defined in this example migration:
create_table :metrics_class, :force => true do |t| t.datetime :finish_time t.integer :number_of_hours t.string :stat_hash end
This will grant the following functionality to the MetricsClass defined above:
MetrifiedClass.element_a_count_name # => "Element A Count" example_finish_time = Time.zone.now.midnight # be sure to use ActiveRecord Time.zone, not Time, so lookups are correct number_of_hours = 7*24 historical_values = MetrifiedClass.historical_values(example_finish_time, 10, :day) # => returns array of 10 aggregations, each over 1 day, older values listed first, ended at the most recent aggregation date (example_finish_time). (:day, :month options) historical_values[0].element_a_count # => generated for the given time frame using the 'element_a_count' method implemented in MetricsClass MetrifiedClass.stat_names # => stat names available, provided in YML file filters = {'type' => ['numbers', 'letters'], 'furriness' => ['furry', 'not_furry']} MetrifiedClass.stat_names(filters) # => return only stat names available from filters stat_names = {'element_1_count', 'element_dog_count'} MetrifiedClass.sorted_stat_names() # => return stat names provided in order, using "stat_order" specified in YAML file. Alphabetical if not specified #Any of these not specified in YAML file will return nil MetrifiedClass.value_type('element_a_count') # => currency - returns type of value (currently only currency). This is useful for knowing how to display MetrifiedClass.show_variance('element_a_count') # => true - whether or not this value's % +/- change over last time period should be displayed MetrifiedClass.value_precision('element_a_count') # if specified in YML file, will round floats using provided precision MetrifiedClass.sorted_stat_names(['element_dog_count', 'element_cat_count', 'element_b_count']) # => ['element_b_count', 'element_dog_count', 'element_cat_count'] ordered as defined by stat_order in yml, alphabetical if not configured
Assuming your application is configured to use Highcharts, optional controller, view, and helper components are included to provide charts with filters. To use:
-
Include MetrifyController module in controller and implement ‘metrified_class’ and ‘set_classname’.
-
Include partials (graph and/or chart) in views
-
Include route in routes.rb as such (where controller for ‘metrified’ model is dashboard): map.dashboard ‘/dashboard/:action.:format’, :controller => ‘dashboard’, :action => ‘index’
Example Controller:
class DashboardController < ApplicationController include MetrifyController def metrify_model Dashboard # Dashboard is the class using the Metrify module end end
Example View for graph:
<style type="text/css" media="screen"> .metrify_graph {width:auto;height:400px;background-color:white} </style> <%= render :partial => "metrify/graph" %>
Depending on the jscript framework you are using, you will also want to define an new instance of Breakdown, defining the Ajax method appropriately:
Prototype:
<script type="text/javascript" charset="utf-8"> window.breakdown = new Breakdown(function(url, complete_fn){ new Ajax.Request(url, {evalJSON : 'force', onComplete : function(text){ complete_fn(text.responseJSON); }}) }); document.observe("dom:loaded", function(){window.breakdown.create_new_chart();}); </script>
Mootools:
<script type="text/javascript" charset="utf-8"> window.breakdown = new Breakdown(function(url, complete_fn){ new Request({url : url, onSuccess : function(text){ complete_fn(text); }}).get(); }); window.addEvent('domready', function(){window.breakdown.create_new_chart();}); </script>
Example View for chart:
<style type="text/css" media="screen"> .metrify_chart {width: auto; background: white; color: black; overflow:auto;} .metrify_chart thead td {font-weight:bold;background-color:#ddd;} .metrify_chart thead td a {color:#116;} .metrify_chart table, th, td {border: 1px solid #ccc; border-width:0 1px 1px 0;} .metrify_chart tr:first-child td {border-top-width:1px;} .metrify_chart td:first-child {border-left-width:1px;} .metrify_chart td {padding:5px;} </style> <%= render :partial => "metrify/chart" %>
A simple view for displaying multiple metrics charts in a email is also available. To use, specify the ‘metrified’ class and create a hash of charts you would like delivered, specifying the chart name and the chart:
@metrified_class = MetrifiedClass @charts = { "Hourly", MetrifiedClass.historical_values(end_time, 168, :hour), "Daily", MetrifiedClass.historical_values(end_time, 7, :day), "Weekly", MetrifiedClass.historical_values(end_time, 52, :week), "Monthly", MetrifiedClass.historical_values(end_time, 12, :month) }
Then, include the partial in your Mailer’s view:
<%= render :partial => ‘metrify/mailer_charts_summary’ %>
-
Fork the project.
-
Make your feature addition or bug fix.
-
Add tests for it. This is important so I don’t break it in a future version unintentionally.
-
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-
Send me a pull request. Bonus points for topic branches.
Copyright © 2010 Stephen Abrams. See LICENSE for details.