redis_analytics 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG.md +12 -0
  2. data/CONTRIBUTING.md +33 -0
  3. data/Guardfile +10 -0
  4. data/README.md +105 -23
  5. data/Rakefile +3 -11
  6. data/TODO.md +12 -0
  7. data/bin/redis_analytics_dashboard +1 -1
  8. data/config.ru +13 -0
  9. data/lib/redis_analytics.rb +5 -1
  10. data/lib/redis_analytics/analytics.rb +17 -203
  11. data/lib/redis_analytics/api.rb +60 -0
  12. data/lib/redis_analytics/configuration.rb +47 -16
  13. data/lib/redis_analytics/dashboard.rb +28 -89
  14. data/lib/redis_analytics/dashboard/public/{favicon.ico → img/favicon.ico} +0 -0
  15. data/lib/redis_analytics/dashboard/public/javascripts/{bootstrap.min.js → vendor/bootstrap/bootstrap.min.js} +0 -0
  16. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-1.9.1.min.js → vendor/jquery-1.9.1.min.js} +0 -0
  17. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-1.2.2.min.js → vendor/jquery-jvectormap-1.2.2.min.js} +0 -0
  18. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-world-mill-en.js → vendor/jquery-jvectormap-world-mill-en.js} +0 -0
  19. data/lib/redis_analytics/dashboard/public/javascripts/{morris.min.js → vendor/morris.min.js} +0 -0
  20. data/lib/redis_analytics/dashboard/public/javascripts/{raphael-min.js → vendor/raphael-min.js} +0 -0
  21. data/lib/redis_analytics/dashboard/views/activity.erb +7 -0
  22. data/lib/redis_analytics/dashboard/views/dialogs/unique_visits.erb +41 -0
  23. data/lib/redis_analytics/dashboard/views/dialogs/visits.erb +40 -0
  24. data/lib/redis_analytics/dashboard/views/footer.erb +1 -3
  25. data/lib/redis_analytics/dashboard/views/header.erb +17 -42
  26. data/lib/redis_analytics/dashboard/views/layout.erb +5 -22
  27. data/lib/redis_analytics/dashboard/views/visits.erb +38 -143
  28. data/lib/redis_analytics/dashboard/views/visits_js.erb +110 -247
  29. data/lib/redis_analytics/dashboard/views/widgets/bounce_rate.erb +3 -0
  30. data/lib/redis_analytics/dashboard/views/widgets/browsers_donut.erb +8 -0
  31. data/lib/redis_analytics/dashboard/views/widgets/first_visits.erb +3 -0
  32. data/lib/redis_analytics/dashboard/views/widgets/page_depth.erb +3 -0
  33. data/lib/redis_analytics/dashboard/views/widgets/referers_donut.erb +8 -0
  34. data/lib/redis_analytics/dashboard/views/widgets/total_page_views.erb +3 -0
  35. data/lib/redis_analytics/dashboard/views/widgets/total_visits.erb +3 -0
  36. data/lib/redis_analytics/dashboard/views/widgets/unique_visits_line.erb +26 -0
  37. data/lib/redis_analytics/dashboard/views/widgets/visit_duration.erb +3 -0
  38. data/lib/redis_analytics/dashboard/views/widgets/visit_spark.erb +23 -0
  39. data/lib/redis_analytics/dashboard/views/widgets/visitor_recency_slices.erb +39 -0
  40. data/lib/redis_analytics/dashboard/views/widgets/visits_area.erb +30 -0
  41. data/lib/redis_analytics/dashboard/views/widgets/visits_donut.erb +8 -0
  42. data/lib/redis_analytics/dashboard/views/widgets/world_map.erb +50 -0
  43. data/lib/redis_analytics/filter.rb +33 -0
  44. data/lib/redis_analytics/helpers.rb +36 -30
  45. data/lib/redis_analytics/metrics.rb +96 -0
  46. data/lib/redis_analytics/time_ext.rb +72 -2
  47. data/lib/redis_analytics/tracker.rb +13 -5
  48. data/lib/redis_analytics/version.rb +1 -1
  49. data/lib/redis_analytics/visit.rb +122 -0
  50. data/redis_analytics.gemspec +19 -14
  51. data/spec/lib/redis_analytics/analytics_spec.rb +59 -0
  52. data/spec/lib/redis_analytics/configuration_spec.rb +158 -0
  53. data/spec/lib/redis_analytics/dashboard_spec.rb +32 -0
  54. data/spec/lib/redis_analytics/filter_spec.rb +34 -0
  55. data/spec/lib/redis_analytics/tracker_spec.rb +20 -0
  56. data/spec/spec_helper.rb +13 -6
  57. data/spec/support/fakeredis.rb +1 -0
  58. data/wsd.png +0 -0
  59. metadata +268 -126
  60. data/lib/redis_analytics/config.ru +0 -10
  61. data/spec/redis_analytics_spec.rb +0 -57
@@ -1,3 +1,4 @@
1
+ require 'date'
1
2
  module TimeExtensions
2
3
  %w[ round floor ceil ].each do |_method|
3
4
  define_method _method do |*args|
@@ -6,14 +7,83 @@ module TimeExtensions
6
7
  end
7
8
  end
8
9
 
10
+ def end_of_year
11
+ Time.local(self.year, 12, 31, 23, 59, 59)
12
+ end
13
+
14
+ def beginning_of_year
15
+ Time.local(self.year, 1, 1, 0, 0, 0)
16
+ end
17
+
18
+ def end_of_month
19
+ Time.local(self.year, self.mon + 1, 1, 23, 59, 59) - 1.day
20
+ end
21
+
22
+ def beginning_of_month
23
+ Date.civil(self.year, self.mon, -1).to_time
24
+ end
25
+
26
+ def beginning_of_day
27
+ Time.local(self.year, self.mon, self.day, 0, 0, 0)
28
+ end
29
+
30
+ def end_of_day
31
+ Time.local(self.year, self.mon, self.day, 23, 59, 59)
32
+ end
33
+
34
+ def beginning_of_day
35
+ Time.local(self.year, self.mon, self.day, 0, 0, 0)
36
+ end
37
+
38
+ def end_of_hour
39
+ Time.local(self.year, self.mon, self.day, self.hour, 59, 59)
40
+ end
41
+
42
+ def beginning_of_hour
43
+ Time.local(self.year, self.mon, self.day, self.hour, 0, 0)
44
+ end
45
+
9
46
  def end_of_minute
10
- change(:sec => 59, :usec => 999999.999)
47
+ Time.local(self.year, self.mon, self.day, self.hour, self.min, 59)
11
48
  end
12
49
 
13
50
  def beginning_of_minute
14
- change(:sec => 0, :usec => 0)
51
+ Time.local(self.year, self.mon, self.day, self.hour, self.min, 0)
15
52
  end
16
53
  end
17
54
 
18
55
  Time.send :include, TimeExtensions
19
56
 
57
+ module FixnumExtensions
58
+
59
+ def minute
60
+ self * 60
61
+ end
62
+
63
+ def hour
64
+ minute * 60
65
+ end
66
+
67
+ def day
68
+ hour * 24
69
+ end
70
+
71
+ def week
72
+ day * 7
73
+ end
74
+
75
+ def month
76
+ day * 30
77
+ end
78
+
79
+ def year
80
+ day * 365
81
+ end
82
+
83
+ def ago(time = Time.now)
84
+ time - self
85
+ end
86
+ end
87
+
88
+ Fixnum.send :include, FixnumExtensions
89
+
@@ -4,23 +4,31 @@ module Rack
4
4
 
5
5
  def initialize(app)
6
6
  @app = Rack::Builder.new do
7
+
8
+ if defined?(Dashboard) and RedisAnalytics.dashboard_endpoint
9
+ puts("WARNING: RedisAnalytics.dashboard_endpoint is set as \"/\"") if RedisAnalytics.dashboard_endpoint == '/'
10
+ map RedisAnalytics.dashboard_endpoint do
11
+ run Dashboard.new
12
+ end
13
+ end
14
+
7
15
  map '/' do
8
16
  run Analytics.new(app)
9
17
  end
10
18
 
11
- if defined? Dashboard and RedisAnalytics.dashboard_endpoint
12
- map RedisAnalytics.dashboard_endpoint do
13
- run Dashboard.new
19
+ if defined?(Api) and RedisAnalytics.api_endpoint
20
+ map RedisAnalytics.api_endpoint do
21
+ run Api.new
14
22
  end
15
23
  end
24
+
16
25
  end
17
26
  end
18
27
 
19
28
  def call(env)
20
29
  @app.call(env)
21
30
  end
22
-
31
+
23
32
  end
24
33
  end
25
34
  end
26
-
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module RedisAnalytics
3
- VERSION = '0.1.0'
3
+ VERSION = '0.6.0'
4
4
  end
5
5
  end
@@ -0,0 +1,122 @@
1
+ module Rack
2
+ module RedisAnalytics
3
+ class Visit
4
+ include Metrics
5
+
6
+ # This class represents one unique visit
7
+ # User may have never visited the site
8
+ # User may have visited before but his visit is expired
9
+ # Everything counted here is unique for a visit
10
+
11
+ # helpers
12
+ def for_each_time_range(t)
13
+ RedisAnalytics.redis_key_timestamps.map{|x, y| t.strftime(x)}.each do |ts|
14
+ yield(ts)
15
+ end
16
+ end
17
+
18
+ def first_visit_info
19
+ cookie = @rack_request.cookies[RedisAnalytics.first_visit_cookie_name]
20
+ return cookie ? cookie.split('.') : []
21
+ end
22
+
23
+ def current_visit_info
24
+ cookie = @rack_request.cookies[RedisAnalytics.current_visit_cookie_name]
25
+ return cookie ? cookie.split('.') : []
26
+ end
27
+
28
+ # method used in analytics.rb to initialize visit
29
+ def initialize(request, response)
30
+ @t = Time.now
31
+ @redis_key_prefix = "#{RedisAnalytics.redis_namespace}:"
32
+ @rack_request = request
33
+ @rack_response = response
34
+ @first_visit_seq = first_visit_info[0] || current_visit_info[0]
35
+ @current_visit_seq = current_visit_info[1]
36
+
37
+ @first_visit_time = first_visit_info[1]
38
+ @last_visit_time = first_visit_info[2]
39
+
40
+ @page_view_seq_no = current_visit_info[2] || 0
41
+ @last_visit_start_time = current_visit_info[3]
42
+ @last_visit_end_time = current_visit_info[4]
43
+ end
44
+
45
+ # called from analytics.rb
46
+ def record
47
+ if @current_visit_seq
48
+ track("visit_time", @t.to_i - @last_visit_end_time.to_i)
49
+ else
50
+ @current_visit_seq ||= counter("visits")
51
+ track("visits", 1) # track core 'visit' metric
52
+ if @first_visit_seq
53
+ track("repeat_visits", 1)
54
+ else
55
+ @first_visit_seq ||= counter("unique_visits")
56
+ track("first_visits", 1)
57
+ track("unique_visits", @first_visit_seq)
58
+ end
59
+ exec_custom_methods('visit') # custom methods that are measured on a per-visit basis
60
+ end
61
+ exec_custom_methods('hit') # custom methods that are measured on a per-page-view (per-hit) basis
62
+ track("page_views", 1) # track core 'page_view' metric
63
+ track("second_page_views", 1) if @page_view_seq_no.to_i == 1 # @last_visit_start_time and (@last_visit_start_time.to_i == @last_visit_end_time.to_i)
64
+ @rack_response
65
+ end
66
+
67
+ def exec_custom_methods(type)
68
+ Metrics.public_instance_methods.each do |meth|
69
+ if m = meth.to_s.match(/^([a-z_]*)_(count|ratio)_per_#{type}$/)
70
+ begin
71
+ return_value = self.send(meth)
72
+ track(m.to_a[1], return_value) if return_value
73
+ rescue => e
74
+ warn "#{meth} resulted in an exception #{e}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # helpers
81
+ def counter(metric_name)
82
+ n = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}#{metric_name}")
83
+ # to debug, uncomment this line
84
+ # puts "COUNT #{metric_name} -> #{n}"
85
+ return n
86
+ end
87
+
88
+ def updated_current_visit_info
89
+ value = [@first_visit_seq.to_i, @current_visit_seq.to_i, @page_view_seq_no.to_i + 1, (@last_visit_start_time || @t).to_i, @t.to_i]
90
+ # to debug, uncomment this line
91
+ # puts "UPDATING VCN COOKIE -> #{value}"
92
+ expires = @t + (RedisAnalytics.visit_timeout.to_i * 60)
93
+ {:value => value.join('.'), :expires => expires}
94
+ end
95
+
96
+ def updated_first_visit_info
97
+ value = [@first_visit_seq.to_i, (@first_visit_time || @t).to_i, @t.to_i]
98
+ # to debug, uncomment this line
99
+ # puts "UPDATING RUCN COOKIE -> #{value}"
100
+ expires = @t + (60 * 60 * 24 * 5) # 5 hours
101
+ {:value => value.join('.'), :expires => expires}
102
+ end
103
+
104
+ def track(metric_name, metric_value)
105
+ n = 0
106
+ RedisAnalytics.redis_connection.hmset("#{@redis_key_prefix}#METRICS", metric_name, metric_value.class)
107
+ for_each_time_range(@t) do |ts|
108
+ key = "#{@redis_key_prefix}#{metric_name}:#{ts}"
109
+ if metric_value.is_a?(Fixnum)
110
+ n = RedisAnalytics.redis_connection.incrby(key, metric_value)
111
+ else
112
+ n = RedisAnalytics.redis_connection.zincrby(key, 1, metric_value)
113
+ end
114
+ end
115
+ # to debug, uncomment this line
116
+ # puts "TRACK #{metric_name} -> #{n}"
117
+ return n
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -1,3 +1,4 @@
1
+
1
2
  # -*- encoding: utf-8 -*-
2
3
  $:.push File.expand_path("../lib", __FILE__)
3
4
  require 'redis_analytics/version'
@@ -5,11 +6,11 @@ require 'redis_analytics/version'
5
6
  Gem::Specification.new do |spec|
6
7
  spec.name = "redis_analytics"
7
8
  spec.version = Rack::RedisAnalytics::VERSION
8
- spec.date = '2013-02-15'
9
+ spec.date = Time.now.strftime('%Y-%m-%d')
9
10
  spec.authors = ["Schubert Cardozo"]
10
11
  spec.email = ["cardozoschubert@gmail.com"]
11
12
  spec.homepage = "https://github.com/saturnine/redis_analytics"
12
- spec.summary = %q{A gem that provides a Redis based web analytics solution for your rack-compliant apps}
13
+ spec.summary = %q{Fast and efficient web analytics for Rack apps}
13
14
  spec.description = %q{A gem that provides a Redis based web analytics solution for your rack-compliant apps. It gives you detailed analytics about visitors, unique visitors, browsers, OS, visitor recency, traffic sources and more}
14
15
 
15
16
  spec.rubyforge_project = "redis_analytics"
@@ -20,18 +21,22 @@ Gem::Specification.new do |spec|
20
21
  spec.default_executable = 'redis_analytics_dashboard'
21
22
  spec.require_paths = ["lib"]
22
23
 
23
- spec.add_runtime_dependency('rack', '>= 1.4.0')
24
- spec.add_runtime_dependency('redis', '>= 3.0.2')
25
- spec.add_runtime_dependency('browser', '>= 0.1.6')
26
- spec.add_runtime_dependency('sinatra', '>= 1.3.3')
27
- spec.add_runtime_dependency('geoip', '>= 1.2.1')
28
- spec.add_runtime_dependency('json', '>= 1.7.7')
29
- spec.add_runtime_dependency('activesupport', '>= 3.2.0')
30
-
31
- spec.add_development_dependency('rake', '>= 10.0.3')
32
- spec.add_development_dependency('rspec', '>= 2.11.0')
33
- spec.add_development_dependency('mocha', '>= 0.12.7')
34
- spec.add_development_dependency('rack-test', '>= 0.6.2')
24
+ spec.add_runtime_dependency('rack')
25
+ spec.add_runtime_dependency('redis')
26
+ spec.add_runtime_dependency('browser')
27
+ spec.add_runtime_dependency('sinatra')
28
+ spec.add_runtime_dependency('sinatra-assetpack')
29
+ spec.add_runtime_dependency('geoip')
30
+ spec.add_runtime_dependency('json')
31
+
32
+ spec.add_development_dependency('fakeredis')
33
+ spec.add_development_dependency('rake')
34
+ spec.add_development_dependency('rspec')
35
+ spec.add_development_dependency('guard-rspec')
36
+ spec.add_development_dependency('mocha')
37
+ spec.add_development_dependency('rack-test')
38
+ spec.add_development_dependency('simplecov')
39
+ spec.add_development_dependency('coveralls')
35
40
 
36
41
  spec.required_ruby_version = '>= 1.9.2'
37
42
  end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::RedisAnalytics::Analytics do
4
+
5
+ subject(:app) {
6
+ Rack::Builder.app do
7
+ use Rack::RedisAnalytics::Analytics
8
+ run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, "Hello!"] }
9
+ end
10
+ }
11
+
12
+ before(:each) do
13
+ clear_cookies
14
+ end
15
+
16
+ # Spec for Cookies
17
+ context "when a user makes 2 visits and the current visit cookie and first visit cookie are not expired" do
18
+ def cookie
19
+ last_response.original_headers['Set-Cookie']
20
+ end
21
+ it "should count as the same visit in the cookie" do
22
+ t1 = Time.now
23
+ Time.stubs(:now).returns(t1)
24
+ get '/'
25
+ cookie.should match("#{Rack::RedisAnalytics.current_visit_cookie_name}=1.1.1.#{t1.to_i}.#{t1.to_i}")
26
+ cookie.should match("#{Rack::RedisAnalytics.first_visit_cookie_name}=1.#{t1.to_i}.#{t1.to_i}")
27
+ t2 = t1 + 5 # just adding 5 seconds
28
+ Time.stubs(:now).returns(t2)
29
+ get '/'
30
+ cookie.should match("#{Rack::RedisAnalytics.current_visit_cookie_name}=1.1.2.#{t1.to_i}.#{t2.to_i}")
31
+ cookie.should match("#{Rack::RedisAnalytics.first_visit_cookie_name}=1.#{t1.to_i}.#{t2.to_i}")
32
+ end
33
+ end
34
+
35
+ context "when a user makes 2 visits, but current visit cookie and first visit cookie are both non-existent" do
36
+ def cookie
37
+ last_response.original_headers['Set-Cookie']
38
+ end
39
+ it "should count as a separate and new visit in the cookie" do
40
+ t1 = Time.now
41
+ Time.stubs(:now).returns(t1)
42
+ get '/'
43
+ cookie.should match("#{Rack::RedisAnalytics.current_visit_cookie_name}=1.1.1.#{t1.to_i}.#{t1.to_i}")
44
+ cookie.should match("#{Rack::RedisAnalytics.first_visit_cookie_name}=1.#{t1.to_i}.#{t1.to_i}")
45
+ clear_cookies
46
+
47
+ t2 = t1 + 5 # just adding 5 seconds
48
+ Time.stubs(:now).returns(t2)
49
+ get '/'
50
+ cookie.should match("#{Rack::RedisAnalytics.current_visit_cookie_name}=2.2.1.#{t2.to_i}.#{t2.to_i}")
51
+ cookie.should match("#{Rack::RedisAnalytics.first_visit_cookie_name}=2.#{t2.to_i}.#{t2.to_i}")
52
+ end
53
+ end
54
+
55
+ context "when a user makes 2 visits, and visit cookie is expired but the returning user cookie exists" do
56
+ it "should count as a separate visit but not a new visit"
57
+ end
58
+
59
+ end
@@ -0,0 +1,158 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::RedisAnalytics::Configuration do
4
+
5
+ context 'property redis_connection' do
6
+ subject(:connection) { Rack::RedisAnalytics.redis_connection }
7
+ it 'should not be nil' do
8
+ connection.should_not be_nil
9
+ end
10
+ it 'should be an instance of Redis' do
11
+ connection.instance_of? Redis
12
+ end
13
+ end
14
+
15
+ context 'property redis_namespace' do
16
+ subject(:namespace) { Rack::RedisAnalytics.redis_namespace }
17
+ it 'should not be nil' do
18
+ namespace.should_not be_nil
19
+ end
20
+ it 'should have an default value' do
21
+ namespace.should be == "_ra_test_namespace"
22
+ end
23
+ it 'can be set to another value' do
24
+ namespace = "test_ra"
25
+ namespace.should be == "test_ra"
26
+ end
27
+ end
28
+
29
+ context 'property first_visit_cookie_name' do
30
+ subject(:return_cookie) { Rack::RedisAnalytics.first_visit_cookie_name }
31
+ it 'should not be nil' do
32
+ return_cookie.should_not be_nil
33
+ end
34
+ it 'should have an default value' do
35
+ return_cookie.should be == "_rucn"
36
+ end
37
+ it 'has an setter method' do
38
+ return_cookie = "rucn"
39
+ return_cookie.should be == "rucn"
40
+ end
41
+ end
42
+
43
+ context 'property current_visit_cookie_name' do
44
+ subject(:current_visit_cookie_name) { Rack::RedisAnalytics.current_visit_cookie_name }
45
+ it 'should not be nil' do
46
+ current_visit_cookie_name.should_not be_nil
47
+ end
48
+ it 'should have an default value' do
49
+ current_visit_cookie_name.should be == "_vcn"
50
+ end
51
+ it 'can be set to another value' do
52
+ current_visit_cookie_name = "test_vcn"
53
+ current_visit_cookie_name.should be == "test_vcn"
54
+ end
55
+ end
56
+
57
+ context 'property visit_timeout' do
58
+ subject(:visit_timeout) { Rack::RedisAnalytics.visit_timeout }
59
+ it 'should not be nil' do
60
+ visit_timeout.should_not be_nil
61
+ end
62
+ it 'should have an default value' do
63
+ visit_timeout.should be == 1
64
+ end
65
+ it 'can be set to another value' do
66
+ visit_timeout = 5
67
+ visit_timeout.should be == 5
68
+ end
69
+ end
70
+
71
+ context 'property visitor_recency_slices' do
72
+ subject(:visitor_recency_slices) { Rack::RedisAnalytics.visitor_recency_slices }
73
+ it 'should not be nil' do
74
+ visitor_recency_slices.should_not be_nil
75
+ end
76
+ it 'should be an Array of Fixnum' do
77
+ visitor_recency_slices.instance_of? Array
78
+ visitor_recency_slices.each do |value|
79
+ value.instance_of? Fixnum
80
+ end
81
+ end
82
+ it 'can be set to another value' do
83
+ visitor_recency_slices = [3, 7, 9]
84
+ expect(visitor_recency_slices).to eq([3, 7, 9])
85
+ end
86
+ end
87
+
88
+ context 'property default_range' do
89
+ subject(:default_range) { Rack::RedisAnalytics.default_range }
90
+ it 'should not be nil' do
91
+ default_range.should_not be_nil
92
+ end
93
+ it 'should be an symbol' do
94
+ default_range.instance_of? Symbol
95
+ end
96
+ it 'should have an value' do
97
+ default_range.should be == :day
98
+ end
99
+ it 'can be set to another value' do
100
+ default_range = :month
101
+ default_range.should be :month
102
+ end
103
+ end
104
+
105
+ context 'property redis_key_timestamps' do
106
+ subject(:redis_key_timestamps) { Rack::RedisAnalytics.redis_key_timestamps }
107
+ it 'should not be nil' do
108
+ redis_key_timestamps.should_not be_nil
109
+ end
110
+ it 'should be an instance of Array' do
111
+ redis_key_timestamps.instance_of? Array
112
+ end
113
+ it 'can be set to another value' do
114
+ redis_key_timestamps = ['one', 'two']
115
+ expect(redis_key_timestamps).to eq(['one','two'])
116
+ end
117
+ end
118
+
119
+ context 'property time_range_formats' do
120
+ subject(:time_range_formats) { Rack::RedisAnalytics.time_range_formats }
121
+ it 'should not be nil' do
122
+ time_range_formats.should_not be_nil
123
+ end
124
+ it 'should be an Array of Array' do
125
+ time_range_formats.instance_of? Array
126
+ time_range_formats.each do |range|
127
+ range.instance_of? Array
128
+ end
129
+ end
130
+ it 'can be set to another value' do
131
+ time_range_formats = "nothing"
132
+ expect(time_range_formats).to eq("nothing")
133
+ end
134
+ end
135
+
136
+ context 'method add_filter' do
137
+ subject(:filters) { Rack::RedisAnalytics.filters }
138
+ it 'should add a new filter' do
139
+ proc = Proc.new {}
140
+ Rack::RedisAnalytics.configure do |c|
141
+ c.add_filter(&proc)
142
+ end
143
+ filters[0].should be_an_instance_of Rack::RedisAnalytics::Filter
144
+ end
145
+ end
146
+
147
+ context 'method add_path_filter' do
148
+ subject(:path_filters) { Rack::RedisAnalytics.path_filters }
149
+ it 'should add a new path filter' do
150
+ path = '/hello'
151
+ Rack::RedisAnalytics.configure do |c|
152
+ c.add_path_filter(path)
153
+ end
154
+ path_filters[0].should be_an_instance_of Rack::RedisAnalytics::PathFilter
155
+ end
156
+ end
157
+
158
+ end