gricer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +84 -0
  3. data/Rakefile +49 -0
  4. data/app/assets/images/gricer/fluid/breadcrumb.png +0 -0
  5. data/app/assets/javascripts/gricer.js.coffee +85 -0
  6. data/app/assets/javascripts/gricer_backend_jquery.js.coffee +352 -0
  7. data/app/assets/javascripts/jquery.flot.js +2599 -0
  8. data/app/assets/javascripts/jquery.flot.pie.js +750 -0
  9. data/app/assets/javascripts/jquery.flot.resize.js +60 -0
  10. data/app/assets/javascripts/jquery.flot.symbol.js +70 -0
  11. data/app/assets/javascripts/worldmap.js +146 -0
  12. data/app/assets/stylesheets/gricer/fluid-jquery-ui.css.scss +1298 -0
  13. data/app/assets/stylesheets/gricer/fluid.css.scss +240 -0
  14. data/app/assets/stylesheets/gricer/helpers/css3.css.scss +21 -0
  15. data/app/controllers/gricer/base_controller.rb +141 -0
  16. data/app/controllers/gricer/capture_controller.rb +42 -0
  17. data/app/controllers/gricer/dashboard_controller.rb +18 -0
  18. data/app/controllers/gricer/requests_controller.rb +42 -0
  19. data/app/controllers/gricer/sessions_controller.rb +24 -0
  20. data/app/helpers/gricer/base_helper.rb +22 -0
  21. data/app/models/gricer/agent.rb +789 -0
  22. data/app/models/gricer/request.rb +239 -0
  23. data/app/models/gricer/session.rb +433 -0
  24. data/app/views/gricer/capture/index.html.erb +1 -0
  25. data/app/views/gricer/dashboard/_menu.html.erb +10 -0
  26. data/app/views/gricer/dashboard/_overview.html.erb +33 -0
  27. data/app/views/gricer/dashboard/index.html.erb +19 -0
  28. data/config/routes.rb +51 -0
  29. data/lib/gricer.rb +36 -0
  30. data/lib/gricer/action_controller/base.rb +28 -0
  31. data/lib/gricer/action_controller/track.rb +132 -0
  32. data/lib/gricer/active_model/statistics.rb +167 -0
  33. data/lib/gricer/config.rb +125 -0
  34. data/lib/gricer/engine.rb +9 -0
  35. data/lib/gricer/localization.rb +3 -0
  36. data/lib/tasks/gricer_tasks.rake +92 -0
  37. data/spec/controllers/gricer/base_controller_spec.rb +207 -0
  38. data/spec/controllers/gricer/capture_controller_spec.rb +44 -0
  39. data/spec/controllers/gricer/dashboard_controller_spec.rb +44 -0
  40. data/spec/controllers/gricer/requests_controller_spec.rb +36 -0
  41. data/spec/controllers/gricer/sessions_controller_spec.rb +37 -0
  42. data/spec/dummy/Rakefile +7 -0
  43. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  44. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  45. data/spec/dummy/app/assets/stylesheets/dashboard.css +4 -0
  46. data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
  47. data/spec/dummy/app/assets/stylesheets/sessions.css +4 -0
  48. data/spec/dummy/app/controllers/application_controller.rb +23 -0
  49. data/spec/dummy/app/controllers/dashboard_controller.rb +19 -0
  50. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  51. data/spec/dummy/app/helpers/dashboard_helper.rb +2 -0
  52. data/spec/dummy/app/views/dashboard/index.html.erb +236 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +16 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +48 -0
  56. data/spec/dummy/config/boot.rb +10 -0
  57. data/spec/dummy/config/cucumber.yml +9 -0
  58. data/spec/dummy/config/database.yml +25 -0
  59. data/spec/dummy/config/environment.rb +5 -0
  60. data/spec/dummy/config/environments/development.rb +27 -0
  61. data/spec/dummy/config/environments/production.rb +51 -0
  62. data/spec/dummy/config/environments/test.rb +39 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/inflections.rb +10 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  67. data/spec/dummy/config/initializers/session_store.rb +8 -0
  68. data/spec/dummy/config/initializers/wrap_parameters.rb +12 -0
  69. data/spec/dummy/config/locales/en.yml +5 -0
  70. data/spec/dummy/config/routes.rb +11 -0
  71. data/spec/dummy/db/schema.rb +241 -0
  72. data/spec/dummy/log/development.log +0 -0
  73. data/spec/dummy/public/404.html +26 -0
  74. data/spec/dummy/public/422.html +26 -0
  75. data/spec/dummy/public/500.html +26 -0
  76. data/spec/dummy/public/favicon.ico +0 -0
  77. data/spec/dummy/script/rails +6 -0
  78. data/spec/helpers/gricer/base_helper_spec.rb +28 -0
  79. data/spec/lib/gricer/action_controller/track_spec.rb +63 -0
  80. data/spec/models/gricer/agent_spec.rb +829 -0
  81. data/spec/models/gricer/request_spec.rb +145 -0
  82. data/spec/models/gricer/session_spec.rb +209 -0
  83. data/spec/routing/capture_routes_spec.rb +6 -0
  84. data/spec/routing/dashboard_routes_spec.rb +9 -0
  85. data/spec/routing/requests_routes_spec.rb +90 -0
  86. data/spec/routing/sessions_routes_spec.rb +115 -0
  87. data/spec/spec_helper.rb +23 -0
  88. metadata +185 -0
@@ -0,0 +1 @@
1
+ <%= params.inspect %>
@@ -0,0 +1,10 @@
1
+ <% items.each do |item| -%>
2
+ <% if item[1].to_sym == :menu -%>
3
+ <div>
4
+ <span><%= item[0] %></span>
5
+ <%= render 'menu', items: item[2] %>
6
+ </div>
7
+ <% else -%>
8
+ <%= link_to item[0], item[2], "data-gricer-#{item[1]}" => true %>
9
+ <% end -%>
10
+ <% end -%>
@@ -0,0 +1,33 @@
1
+ <div class='overview'>
2
+ <h2>
3
+ Overview for <%=l @stat_from%>
4
+ <% unless @stat_from == @stat_thru -%>
5
+ to <%=l @stat_thru%>
6
+ <% end -%>
7
+ </h2>
8
+
9
+ <p>
10
+ <% sessions_count = sessions.count.try(:to_f) -%>
11
+ <%=t '.sessions', count: sessions_count.to_i %>
12
+ </p>
13
+ <p>
14
+ <%=t '.new_sessions', count: sessions.new_visits.count %>
15
+ </p>
16
+ <p>
17
+ <%=t '.new_visitors', percentage: format_percentage(sessions.new_visitors) %>
18
+ </p>
19
+ <p>
20
+ <% request_count = requests.count.try(:to_f) -%>
21
+ <%=t '.requests', count: request_count.to_i %>
22
+ </p>
23
+ <p>
24
+ <%=t '.requests_per_session', count: '%3.2f' % sessions.requests_per_session %>
25
+ </p>
26
+
27
+ <p>
28
+ <%=t '.bounce_rate', percentage: format_percentage(sessions.bounce_rate) %>
29
+ </p>
30
+ <p>
31
+ <%=t '.avg_duration', time: format_seconds(sessions.avg_duration) %>
32
+ </p>
33
+ </div>
@@ -0,0 +1,19 @@
1
+ <div class="gricer-stat">
2
+ <div id="gricer-header">
3
+ <span class="path"></span>
4
+ <span class="dates">
5
+ <%= form_tag do -%>
6
+ <%= text_field_tag :from, @stat_from, id: 'gricer-from-field' -%>
7
+ -
8
+ <%= text_field_tag :thru, @stat_thru, id: 'gricer-thru-field' %>
9
+ <% end -%>
10
+ </span>
11
+ </div>
12
+ <div id="gricer-menu">
13
+ <%= render 'menu', items: ::Gricer.config.admin_menu %>
14
+ </div>
15
+
16
+ <div id="gricer-container"></div>
17
+
18
+ <%= javascript_include_tag :gricer_backend_jquery %>
19
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,51 @@
1
+ Rails.application.routes.draw do
2
+ post 'gricer_capture/:id' => 'gricer/capture#index', as: :gricer_capture
3
+
4
+ scope ::Gricer.config.admin_prefix do
5
+ root to: "gricer/dashboard#index", as: :gricer_root
6
+ post 'overview' => "gricer/dashboard#overview", as: :gricer_overview
7
+
8
+ scope 'sessions' do
9
+ [
10
+ 'agent.name', 'agent.os', 'agent.full_version', 'agent.major_version', 'agent.engine_name', 'agent.engine_version',
11
+ 'javascript', 'java', 'silverlight_version', 'silverlight_major_version', 'flash_version', 'flash_major_version',
12
+ 'country', 'city', 'domain', 'screen_size', 'screen_width', 'screen_height', 'screen_depth',
13
+ 'requested_locale_major', 'requested_locale_minor', 'requested_locale',
14
+ ].each do |field|
15
+ name = field.gsub('.', '_')
16
+ get "#{name}_process" => "gricer/sessions#process_stats", field: field, as: "gricer_sessions_#{name}_process"
17
+ get "#{name}_spread" => "gricer/sessions#spread_stats", field: field, as: "gricer_sessions_#{name}_spread"
18
+ end
19
+ end
20
+
21
+ scope 'requests' do
22
+ [
23
+ 'agent.name', 'agent.os', 'agent.full_version', 'agent.major_version', 'agent.engine_name', 'agent.engine_version',
24
+ 'host', 'path', 'method', 'protocol', 'entry_path',
25
+ 'referer_protocol', 'referer_host', 'referer_path', 'referer_params',
26
+ 'search_engine', 'search_query',
27
+ ].each do |field|
28
+ name = field.gsub('.', '_')
29
+ get "#{name}_process" => "gricer/requests#process_stats", field: field, as: "gricer_requests_#{name}_process"
30
+ get "#{name}_spread" => "gricer/requests#spread_stats", field: field, as: "gricer_requests_#{name}_spread"
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ # Gricer::Engine.routes.draw do
37
+ # root to: "dashboard#index"
38
+ #
39
+ # scope 'sessions' do
40
+ # [
41
+ # 'agent.name', 'agent.os', 'agent.full_version', 'agent.major_version', 'agent.engine_name', 'agent.engine_version',
42
+ # 'javascript', 'java', 'silverlight_version', 'silverlight_major_version', 'flash_version', 'flash_major_version',
43
+ # 'country', 'city', 'domain', 'screen_size', 'screen_width', 'screen_height', 'screen_depth',
44
+ # 'requested_major_locale', 'requested_minor_locale', 'requested_locale',
45
+ # ].each do |field|
46
+ # name = field.gsub('.', '_')
47
+ # get "#{name}_process" => "sessions#process_stats", field: field, as: "gricer_session_#{name}_process"
48
+ # get "#{name}_spread" => "sessions#spread_stats", field: field, as: "gricer_session_#{name}_spread"
49
+ # end
50
+ # end
51
+ # end
data/lib/gricer.rb ADDED
@@ -0,0 +1,36 @@
1
+ require "gricer/config"
2
+ require "gricer/engine"
3
+ require "gricer/localization"
4
+
5
+ require 'gricer/action_controller/base'
6
+ require 'gricer/action_controller/track'
7
+
8
+ require 'gricer/active_model/statistics'
9
+
10
+ # Gricer is a web analytics gem for Rails 3.1 and beyond
11
+ module Gricer
12
+ class << self
13
+ # To access the actual configuration of your Gricer, you can call this function.
14
+ #
15
+ # An example would be <tt>Gricer.config.table_name_prefix = 'stats_'</tt>
16
+ #
17
+ # See Gricer::Config for configuration options.
18
+ #
19
+ # @return [Gricer::Config] The actual configuration instance of Gricer
20
+ # @see Gricer::Config
21
+ def config
22
+ @config ||= Config.new
23
+ end
24
+
25
+ # To initialize Gricer it is handy to give it a block of options.
26
+ #
27
+ # See Gricer::Config for configuration options.
28
+ #
29
+ # @yield (config) The actual configuration instance of Gricer
30
+ # @return [Gricer::Config] The actual configuration instance of Gricer
31
+ # @see Gricer::Config
32
+ def configure(&block)
33
+ config.configure(&block)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ module Gricer
2
+ # Gricer's ActionController enhancements
3
+ module ActionController
4
+ # Gricer's ActionController base enhancements
5
+ module Base
6
+
7
+ # Include the helper functions into controllers.
8
+ def self.included(base)
9
+ base.module_eval do
10
+ helper_attr :gricer_user_id
11
+ end
12
+ end
13
+
14
+ # This function is a stub for the mechanism to include the current
15
+ # users id into the statistics of gricer.
16
+ # @example The most common case would be to pass the current_user's id. So this is the code you should add to your appliction_controller
17
+ # def gricer_user_id
18
+ # current_user.try(:id)
19
+ # end
20
+ # @return [Integer]
21
+ def gricer_user_id
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ ActionController::Base.send :include, Gricer::ActionController::Base
@@ -0,0 +1,132 @@
1
+ module Gricer
2
+ # Around-Filter for tracking requests in Gricer
3
+ class TrackRequestFilter
4
+ # Around-Filter for tracking requests in Gricer
5
+ # @param controller The controller from which this Filter was included.
6
+ # @yield The controller's code block
7
+ def self.filter(controller, &block)
8
+ if controller.controller_path =~ /^gricer\// or controller.request.path =~ ::Gricer.config.exclude_paths
9
+ Rails.logger.debug "Gricer Track Request: Do not track '#{controller.controller_path}##{controller.action_name}' by config"
10
+ block.call
11
+ return
12
+ end
13
+
14
+ status = nil
15
+
16
+ options = {
17
+ request: controller.request,
18
+ controller: controller.controller_path,
19
+ action: controller.action_name,
20
+ params: controller.params,
21
+ session_id: controller.session[:gricer_session],
22
+ locale: I18n.locale
23
+ }
24
+
25
+ if controller.gricer_user_id
26
+ options[:user_id] = controller.gricer_user_id
27
+ end
28
+
29
+ options.keys.each do |key|
30
+ Rails.logger.debug key
31
+ end
32
+
33
+ gricer_request = ::Gricer::Request.create options
34
+ controller.gricer_request = gricer_request
35
+ controller.session[:gricer_session] = gricer_request.session_id
36
+
37
+ begin
38
+ benchmark = Benchmark.measure(&block)
39
+
40
+ gricer_request.update_attributes(
41
+ status_code: controller.response.status,
42
+ content_type: controller.response.content_type,
43
+ body_size: controller.response.body.size,
44
+ system_time: (benchmark.cstime*1000).to_i,
45
+ user_time: (benchmark.cutime*1000).to_i,
46
+ total_time: (benchmark.total*1000).to_i,
47
+ real_time: (benchmark.real*1000).to_i
48
+ )
49
+ rescue
50
+ gricer_request.update_attributes(
51
+ status_code: 500,
52
+ content_type: controller.response.content_type,
53
+ body_size: controller.response.body.size
54
+ )
55
+ raise
56
+ end
57
+ end
58
+ end
59
+
60
+ # Helper Method to include Javascript code to capture extra values like screen size
61
+ module TrackHelper
62
+ # Include Gricer's Javascript code to track values like screen size.
63
+ #
64
+ # You should include this at the end of your layout file. For pages matching the
65
+ # Gricer::Config.exclude_paths expression, nothing will be added to your page.
66
+ #
67
+ # @example For html.erb add at the end of your layout file:
68
+ # <html>
69
+ # <head>[...]</head>
70
+ # <body>
71
+ # [...]
72
+ # <%= gricer_track_tag %>
73
+ # </body>
74
+ # </html>
75
+ #
76
+ # @example For html.haml add at the end of your layout file:
77
+ # %html
78
+ # %head
79
+ # [...]
80
+ # %body
81
+ # [...]
82
+ # = gricer_track_tag
83
+
84
+ def gricer_track_tag
85
+ if gricer_request and defined?(gricer_capture_path)
86
+ content_tag :script, "jQuery(function($) {$.post('#{gricer_capture_path(gricer_request.id)}', Gricer.prepareValues());});", type: 'text/javascript'
87
+ end
88
+ end
89
+ end
90
+
91
+ module ActionController
92
+ # Gricer's Tracker module for ActionController
93
+ #
94
+ # To include the Tracker module into ActionController add
95
+ # gricer_track_requests to your ApplicationController or
96
+ # to any Controller you want to track with Gricer.
97
+ #
98
+ # @example
99
+ # class ApplicationController < ActionController::Base
100
+ # protect_from_forgery
101
+ # gricer_track_requests
102
+ # [...]
103
+ # end
104
+
105
+ module Tracker
106
+ # Include the helper functions and around_filter into controllers.
107
+ def self.included(base)
108
+ base.append_around_filter TrackRequestFilter
109
+ base.helper TrackHelper
110
+ base.helper_method :gricer_request
111
+ end
112
+
113
+ # Set the actual gricer request instance.
114
+ # @param gricer_request [Gricer::Request] The gricer request to be set as actual request instance.
115
+ def gricer_request=(gricer_request)
116
+ @gricer_request = gricer_request
117
+ end
118
+
119
+ # Get the actual gricer request instance.
120
+ # @return [Gricer::Request]
121
+ def gricer_request
122
+ @gricer_request
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ class ActionController::Base
129
+ def self.gricer_track_requests(options = {})
130
+ include Gricer::ActionController::Tracker
131
+ end
132
+ end
@@ -0,0 +1,167 @@
1
+ module Gricer
2
+ # Gricer's ActiveModel enhancements
3
+ module ActiveModel
4
+ # Gricer's statistics enhancements for ActiveModel
5
+ #
6
+ # To add statistics to an ActiveRecord just include the module:
7
+ # class SomeDataModel < ::ActiveRecord::Base
8
+ # include ActiveModel::Statistics
9
+ # [...]
10
+ # end
11
+ #
12
+ # This module provides two major enhancements:
13
+ #
14
+ # == Extendend attribute names
15
+ #
16
+ # You can use relation attributes by using a dot in your attribute name.
17
+ #
18
+ # *Example:*
19
+ #
20
+ # For getting the name of the attribute name of Agent associated to the Request:
21
+ # Gricer::Request.human_attributes('agent.name')
22
+ #
23
+ # == Statistics functions and filters
24
+ #
25
+ # Methods for filtering, grouping, and counting records
26
+ module Statistics
27
+ # Extend the ActiveModel with the statistics class methods.
28
+ # @see ClassMethods
29
+ def self.included(base)
30
+ base.extend ClassMethods
31
+ end
32
+
33
+ # Gricer's statistics enhancements for ActiveModel
34
+ #
35
+ # == Extended attribute names
36
+ # You can use relation attributes by using a dot in your attribute name.
37
+ #
38
+ # @example For getting the name of the attribute name of Agent associated to the Request:
39
+ # Gricer::Request.human_attributes('agent.name')
40
+ module ClassMethods
41
+ # Filter records for the time between from and thru
42
+ # @param from [Date, Time] Only get records on or after this point of time
43
+ # @param thru [Date, Time] Only get records before this point of time
44
+ # @return [ActiveRecord::Relation]
45
+ def between(from, thru)
46
+ #self.where(self.table_name => {:created_at.gte => from, :created_at.lt => thru})
47
+ self.where("\"#{table_name}\".\"created_at\" >= ? AND \"#{table_name}\".\"created_at\" < ?", from, thru)
48
+ end
49
+
50
+ # Filter records for the dates between from and thru
51
+ # @param from [Date] Only get records on or after this date
52
+ # @param thru [Date] Only get records on or before this date
53
+ # @return [ActiveRecord::Relation]
54
+ def between_dates(from, thru)
55
+ self.between(from.to_date.to_time, thru.to_date.to_time+1.day)
56
+ end
57
+
58
+ # Group records by attribute.
59
+ #
60
+ # @param attribute [String, Symbol] Attribute to group the records for. You can use gricers extended attribute names.
61
+ # @return [ActiveRecord::Relation]
62
+ def grouped_by(attribute)
63
+ parts = attribute.to_s.split('.')
64
+ if parts[1].blank?
65
+ self.group(attribute)
66
+ .select(attribute)
67
+ else
68
+ if association = self.reflect_on_association(parts[0].to_sym)
69
+ self.includes(association.name)
70
+ .group("\"#{association.table_name}\".\"#{parts[1]}\"")
71
+ .select("\"#{association.table_name}\".\"#{parts[1]}\"")
72
+ else
73
+ raise "Association '#{parts[0]}' not found on #{self.name}"
74
+ end
75
+ end
76
+ end
77
+
78
+ # Count records by attribute.
79
+ #
80
+ # @param attribute [String, Symbol] Attribute name to count for. You can use gricers extended attribute names.
81
+ # @return [ActiveRecord::Relation]
82
+ def count_by(attribute)
83
+ self.grouped_by(attribute).count(:id).sort{ |a,b| b[1] <=> a[1] }
84
+ end
85
+
86
+ # Filter records by attribute's value.
87
+ #
88
+ # @example
89
+ # some_relation.filter('agent.name', 'Internet Explorer').filter('agent.os', 'Windows')
90
+ # @param attribute [String, Symbol] Attribute name to filter. You can use gricers extended attribute names.
91
+ # @param value Attribute value to filter.
92
+ # @return [ActiveRecord::Relation]
93
+
94
+ def filter_by(attribute, value)
95
+ return self if attribute.blank?
96
+
97
+ Rails.logger.debug "Attr: #{attribute}, Value: #{value}"
98
+
99
+ parts = attribute.to_s.split('.')
100
+ if parts[1].blank?
101
+ self.where(attribute => value)
102
+ else
103
+ if association = self.reflect_on_association(parts[0].to_sym)
104
+ self.includes(association.name)
105
+ .where(association.table_name => {parts[1] => value})
106
+ else
107
+ raise "Association '#{parts[0]}' not found on #{self.name}"
108
+ end
109
+ end
110
+ end
111
+
112
+ # Extends ActiveModel::Translation#human_attribute_name to use Gricer's extended attribute names.
113
+ #
114
+ # @param attribute [String, Symbol] Attribute name to get human name for. You can use gricers extended attribute names.
115
+ # @param options [Hash] See ActiveModel::Translation for possible options.
116
+ # @return [String]
117
+ def human_attribute_name(attribute, options = {})
118
+ parts = attribute.to_s.split('.')
119
+ if parts[1].blank?
120
+ super attribute, options
121
+ else
122
+ if association = self.reflect_on_association(parts[0].to_sym)
123
+ association.active_record.human_attribute_name parts[1], options
124
+ else
125
+ parts[1].to_s.humanize
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ # Return hash for values of an attribute within the given time
132
+ #
133
+ #
134
+ # @example Get agent.name statistics for 2011-07-05 08:00 until 2011-07-06 08:00
135
+ # Gricer::Request.stat('agent.name', '2011-07-05 08:00', '2011-07-06 08:00')
136
+ # @example Get agent.engine_version statistics for agents with engine_name = 'Presto' and values between 2011-07-05 08:00 and last hour with values in 15 minutes steps
137
+ # Gricer::Request.where('gricer_agents.engine_name = ?', 'Presto').stat('agent.engine_version', '2011-07-05 08:00', Time.now - 1.hour, 15.minutes)
138
+ #
139
+ # @param attribute [String, Symbol] Attribute name to get data for. You can use gricers extended attribute names.
140
+ # @param from [Date, Time] Only get records on or after this point of time
141
+ # @param thru [Date, Time] Only get records before this point of time
142
+ # @param step [Integer] Time interval for grouping values
143
+ # @return [Hash]
144
+ def stat(attribute, from, thru, step = 1.hour)
145
+ query = self.grouped_by(attribute)
146
+
147
+ from = from.to_date.to_time
148
+ thru = thru.to_date.to_time+1.day
149
+
150
+ now = from
151
+
152
+ stats = {}
153
+
154
+ while now < thru do
155
+ self.between(now, now + step).count_by(attribute).each do |value|
156
+ stats[value[0]] ||= []
157
+ stats[value[0]] << [now.to_i*1000, value[1]]
158
+ end
159
+ now += step
160
+ end
161
+
162
+ return stats
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end