analytical 0.3.0

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 (74) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +46 -0
  6. data/VERSION +1 -0
  7. data/analytical.gemspec +124 -0
  8. data/example/.gitignore +4 -0
  9. data/example/Gemfile +27 -0
  10. data/example/README +244 -0
  11. data/example/Rakefile +10 -0
  12. data/example/app/controllers/application_controller.rb +6 -0
  13. data/example/app/controllers/page_controller.rb +11 -0
  14. data/example/app/helpers/application_helper.rb +2 -0
  15. data/example/app/helpers/page_helper.rb +2 -0
  16. data/example/app/views/layouts/application.html.erb +17 -0
  17. data/example/app/views/page/index.html.erb +5 -0
  18. data/example/app/views/page/test_a.html.erb +10 -0
  19. data/example/app/views/page/test_b.html.erb +6 -0
  20. data/example/config.ru +4 -0
  21. data/example/config/analytical.yml +6 -0
  22. data/example/config/application.rb +46 -0
  23. data/example/config/boot.rb +6 -0
  24. data/example/config/database.yml +22 -0
  25. data/example/config/environment.rb +5 -0
  26. data/example/config/environments/development.rb +19 -0
  27. data/example/config/environments/production.rb +42 -0
  28. data/example/config/environments/test.rb +32 -0
  29. data/example/config/initializers/backtrace_silencers.rb +7 -0
  30. data/example/config/initializers/inflections.rb +10 -0
  31. data/example/config/initializers/mime_types.rb +5 -0
  32. data/example/config/initializers/secret_token.rb +7 -0
  33. data/example/config/initializers/session_store.rb +8 -0
  34. data/example/config/locales/en.yml +5 -0
  35. data/example/config/routes.rb +64 -0
  36. data/example/db/seeds.rb +7 -0
  37. data/example/doc/README_FOR_APP +2 -0
  38. data/example/lib/tasks/.gitkeep +0 -0
  39. data/example/public/404.html +26 -0
  40. data/example/public/422.html +26 -0
  41. data/example/public/500.html +26 -0
  42. data/example/public/favicon.ico +0 -0
  43. data/example/public/images/rails.png +0 -0
  44. data/example/public/javascripts/application.js +2 -0
  45. data/example/public/javascripts/controls.js +965 -0
  46. data/example/public/javascripts/dragdrop.js +974 -0
  47. data/example/public/javascripts/effects.js +1123 -0
  48. data/example/public/javascripts/prototype.js +4874 -0
  49. data/example/public/javascripts/rails.js +118 -0
  50. data/example/public/robots.txt +5 -0
  51. data/example/public/stylesheets/.gitkeep +0 -0
  52. data/example/script/rails +9 -0
  53. data/example/test/functional/page_controller_test.rb +19 -0
  54. data/example/test/performance/browsing_test.rb +9 -0
  55. data/example/test/test_helper.rb +13 -0
  56. data/example/test/unit/helpers/page_helper_test.rb +4 -0
  57. data/example/vendor/plugins/.gitkeep +0 -0
  58. data/lib/analytical.rb +52 -0
  59. data/lib/analytical/api.rb +90 -0
  60. data/lib/analytical/base.rb +34 -0
  61. data/lib/analytical/clicky.rb +51 -0
  62. data/lib/analytical/console.rb +37 -0
  63. data/lib/analytical/google.rb +34 -0
  64. data/lib/analytical/kiss_metrics.rb +35 -0
  65. data/rails/init.rb +1 -0
  66. data/spec/analytical/api_spec.rb +122 -0
  67. data/spec/analytical/clicky_spec.rb +49 -0
  68. data/spec/analytical/google_spec.rb +38 -0
  69. data/spec/analytical/kiss_metrics_spec.rb +44 -0
  70. data/spec/analytical_spec.rb +64 -0
  71. data/spec/config/analytical.yml +6 -0
  72. data/spec/spec.opts +5 -0
  73. data/spec/spec_helper.rb +22 -0
  74. metadata +165 -0
@@ -0,0 +1,118 @@
1
+ document.observe("dom:loaded", function() {
2
+ function handleRemote(element) {
3
+ var method, url, params;
4
+
5
+ if (element.tagName.toLowerCase() === 'form') {
6
+ method = element.readAttribute('method') || 'post';
7
+ url = element.readAttribute('action');
8
+ params = element.serialize(true);
9
+ } else {
10
+ method = element.readAttribute('data-method') || 'get';
11
+ url = element.readAttribute('href');
12
+ params = {};
13
+ }
14
+
15
+ var event = element.fire("ajax:before");
16
+ if (event.stopped) return false;
17
+
18
+ new Ajax.Request(url, {
19
+ method: method,
20
+ parameters: params,
21
+ asynchronous: true,
22
+ evalScripts: true,
23
+
24
+ onLoading: function(request) { element.fire("ajax:loading", {request: request}); },
25
+ onLoaded: function(request) { element.fire("ajax:loaded", {request: request}); },
26
+ onInteractive: function(request) { element.fire("ajax:interactive", {request: request}); },
27
+ onComplete: function(request) { element.fire("ajax:complete", {request: request}); },
28
+ onSuccess: function(request) { element.fire("ajax:success", {request: request}); },
29
+ onFailure: function(request) { element.fire("ajax:failure", {request: request}); }
30
+ });
31
+
32
+ element.fire("ajax:after");
33
+ }
34
+
35
+ function handleMethod(element) {
36
+ var method, url, token_name, token;
37
+
38
+ method = element.readAttribute('data-method');
39
+ url = element.readAttribute('href');
40
+ csrf_param = $$('meta[name=csrf-param]').first();
41
+ csrf_token = $$('meta[name=csrf-token]').first();
42
+
43
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
44
+ element.parentNode.appendChild(form);
45
+
46
+ if (method != 'post') {
47
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
48
+ form.appendChild(field);
49
+ }
50
+
51
+ if (csrf_param) {
52
+ var param = csrf_param.readAttribute('content');
53
+ var token = csrf_token.readAttribute('content');
54
+ var field = new Element('input', { type: 'hidden', name: param, value: token });
55
+ form.appendChild(field);
56
+ }
57
+
58
+ form.submit();
59
+ }
60
+
61
+ $(document.body).observe("click", function(event) {
62
+ var message = event.findElement().readAttribute('data-confirm');
63
+ if (message && !confirm(message)) {
64
+ event.stop();
65
+ return false;
66
+ }
67
+
68
+ var element = event.findElement("a[data-remote]");
69
+ if (element) {
70
+ handleRemote(element);
71
+ event.stop();
72
+ return true;
73
+ }
74
+
75
+ var element = event.findElement("a[data-method]");
76
+ if (element) {
77
+ handleMethod(element);
78
+ event.stop();
79
+ return true;
80
+ }
81
+ });
82
+
83
+ // TODO: I don't think submit bubbles in IE
84
+ $(document.body).observe("submit", function(event) {
85
+ var element = event.findElement(),
86
+ message = element.readAttribute('data-confirm');
87
+ if (message && !confirm(message)) {
88
+ event.stop();
89
+ return false;
90
+ }
91
+
92
+ var inputs = element.select("input[type=submit][data-disable-with]");
93
+ inputs.each(function(input) {
94
+ input.disabled = true;
95
+ input.writeAttribute('data-original-value', input.value);
96
+ input.value = input.readAttribute('data-disable-with');
97
+ });
98
+
99
+ var element = event.findElement("form[data-remote]");
100
+ if (element) {
101
+ handleRemote(element);
102
+ event.stop();
103
+ }
104
+ });
105
+
106
+ $(document.body).observe("ajax:after", function(event) {
107
+ var element = event.findElement();
108
+
109
+ if (element.tagName.toLowerCase() === 'form') {
110
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
111
+ inputs.each(function(input) {
112
+ input.value = input.readAttribute('data-original-value');
113
+ input.writeAttribute('data-original-value', null);
114
+ input.disabled = false;
115
+ });
116
+ }
117
+ });
118
+ });
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
File without changes
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENV_PATH = File.expand_path('../../config/environment', __FILE__)
5
+ BOOT_PATH = File.expand_path('../../config/boot', __FILE__)
6
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
7
+
8
+ require BOOT_PATH
9
+ require 'rails/commands'
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class PageControllerTest < ActionController::TestCase
4
+ test "should get index" do
5
+ get :index
6
+ assert_response :success
7
+ end
8
+
9
+ test "should get test_a" do
10
+ get :test_a
11
+ assert_response :success
12
+ end
13
+
14
+ test "should get test_b" do
15
+ get :test_b
16
+ assert_response :success
17
+ end
18
+
19
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'rails/performance_test_help'
3
+
4
+ # Profiling results for each test method are written to tmp/performance.
5
+ class BrowsingTest < ActionDispatch::PerformanceTest
6
+ def test_homepage
7
+ get '/'
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
7
+ #
8
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
9
+ # -- they do not yet inherit this setting
10
+ fixtures :all
11
+
12
+ # Add more helper methods to be used by all tests here...
13
+ end
@@ -0,0 +1,4 @@
1
+ require 'test_helper'
2
+
3
+ class PageHelperTest < ActionView::TestCase
4
+ end
File without changes
@@ -0,0 +1,52 @@
1
+ Dir.glob(File.dirname(__FILE__)+'/analytical/*.rb').each do |f|
2
+ require f
3
+ end
4
+
5
+ module Analytical
6
+
7
+ # any method placed here will apply to ActionController::Base
8
+ def analytical(options={})
9
+ send :include, InstanceMethods
10
+ send :helper_method, :analytical
11
+ send :cattr_accessor, :analytical_options
12
+
13
+ self.analytical_options = options.reverse_merge({
14
+ :modules=>[],
15
+ :development_modules=>[:console],
16
+ :disable_if=>Proc.new { !Rails.env.production? },
17
+ })
18
+
19
+ config_options = {}
20
+ File.open("#{RAILS_ROOT}/config/analytical.yml") do |f|
21
+ config_options = YAML::load(ERB.new(f.read).result).symbolize_keys
22
+ config_options.each do |k,v|
23
+ config_options[k] = v.symbolize_keys
24
+ end
25
+ end if File.exists?("#{RAILS_ROOT}/config/analytical.yml")
26
+
27
+ self.analytical_options = self.analytical_options.merge config_options
28
+
29
+ if self.analytical_options[:disable_if].call
30
+ self.analytical_options[:modules] = self.analytical_options[:development_modules]
31
+ end
32
+ self.analytical_options[:modules].each do |m|
33
+ Analytical::Api.send :include, "Analytical::#{m.to_s.camelize}".constantize
34
+ end
35
+ end
36
+
37
+ module InstanceMethods
38
+ # any method placed here will apply to instances
39
+
40
+ def analytical
41
+ options = self.class.analytical_options.merge({
42
+ :ssl => request.ssl?
43
+ })
44
+ @analytical ||= Analytical::Api.new options
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ if defined?(ActionController::Base)
51
+ ActionController::Base.extend Analytical
52
+ end
@@ -0,0 +1,90 @@
1
+ module Analytical
2
+
3
+ class Api
4
+ attr_accessor :options, :modules
5
+
6
+ def initialize(options={})
7
+ @options = options
8
+ @modules = @options[:modules].inject(ActiveSupport::OrderedHash.new) do |h, m|
9
+ h[m] = "Analytical::#{m.to_s.camelize}::Api".constantize.new(self, @options[m] || {})
10
+ h
11
+ end
12
+ end
13
+
14
+ #
15
+ # Catch commands such as :track, :identify and send them on to all of the modules.
16
+ # Or... if a module name is passed, return that module so it can be used directly, ie:
17
+ # analytical.console.go 'make', :some=>:cookies
18
+ #
19
+ def method_missing(method, *args, &block)
20
+ if @modules.keys.include?(method.to_sym)
21
+ @modules[method.to_sym]
22
+ else
23
+ process_command method.to_sym, *args
24
+ end
25
+ end
26
+
27
+
28
+ #
29
+ # Delegation class that passes methods to
30
+ #
31
+ class ImmediateDelegateHelper
32
+ def initialize(_parent)
33
+ @parent = _parent
34
+ end
35
+ def method_missing(method, *args, &block)
36
+ @parent.modules.values.collect do |m|
37
+ m.send method, *args
38
+ end.join("\n")
39
+ end
40
+ end
41
+
42
+ #
43
+ # Returns a new delegation object for immediate processing of a command
44
+ #
45
+ def now
46
+ ImmediateDelegateHelper.new(self)
47
+ end
48
+
49
+ #
50
+ # These methods return the javascript that should be inserted into each section of your layout
51
+ #
52
+ def head_javascript
53
+ [init_javascript(:head), tracking_javascript(:head)].delete_if{|s| s.blank?}.join("\n")
54
+ end
55
+ def body_prepend_javascript
56
+ [init_javascript(:body_prepend), tracking_javascript(:body_prepend)].delete_if{|s| s.blank?}.join("\n")
57
+ end
58
+ def body_append_javascript
59
+ [init_javascript(:body_append), tracking_javascript(:body_append)].delete_if{|s| s.blank?}.join("\n")
60
+ end
61
+
62
+ private
63
+
64
+ def process_command(command, *args)
65
+ @modules.values.each do |m|
66
+ m.queue command, *args
67
+ end
68
+ end
69
+
70
+ def tracking_javascript(location)
71
+ commands = []
72
+ @modules.each do |name, m|
73
+ commands += m.process_queued_commands if m.tracking_command_location==location
74
+ end
75
+ commands = commands.delete_if{|c| c.blank? || c.empty?}
76
+ unless commands.empty?
77
+ commands.unshift "<script type='text/javascript'>"
78
+ commands << "</script>"
79
+ end
80
+ commands.join("\n")
81
+ end
82
+
83
+ def init_javascript(location)
84
+ @modules.values.collect do |m|
85
+ m.init_javascript(location)
86
+ end.compact.join("\n")
87
+ end
88
+ end
89
+
90
+ end
@@ -0,0 +1,34 @@
1
+ module Analytical
2
+ module Base
3
+ module Api
4
+ attr_reader :tracking_command_location, :parent, :options
5
+ attr_accessor :commands
6
+
7
+ def initialize(_parent, _options={})
8
+ @parent = _parent
9
+ @options = _options
10
+ @tracking_command_location = :body_prepend
11
+ @commands = []
12
+ end
13
+
14
+ def track(*args); ''; end
15
+ def identify(id, *args); ''; end
16
+ def event(name, *args); ''; end
17
+ def init_javascript(location); {}; end
18
+
19
+ def queue(*args)
20
+ if args.first==:identify
21
+ @commands.unshift args
22
+ else
23
+ @commands << args
24
+ end
25
+ end
26
+ def process_queued_commands
27
+ command_strings = @commands.collect {|c| send(*c) }
28
+ @commands = []
29
+ command_strings
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ module Analytical
2
+ module Clicky
3
+ class Api
4
+ include Analytical::Base::Api
5
+
6
+ def initialize(parent, options={})
7
+ super
8
+ @tracking_command_location = :body_append
9
+ end
10
+
11
+ def init_javascript(location)
12
+ return '' unless location==:body_append
13
+
14
+ protocol = options[:ssl] ? 'https' : 'http'
15
+
16
+ js = <<-HTML
17
+ <!-- Analytical Init: Clicky -->
18
+ <script src="#{protocol}://static.getclicky.com/js" type="text/javascript"></script>
19
+ <script type="text/javascript">clicky.init('#{@options[:key]}');</script>
20
+ <noscript><p><img alt="Clicky" width="1" height="1" src="#{protocol}://in.getclicky.com/#{@options[:key]}ns.gif" /></p></noscript>
21
+ HTML
22
+
23
+ identify_commands = []
24
+ @commands.each do |c|
25
+ if c[0] == :identify
26
+ identify_commands << identify(*c[1..-1])
27
+ end
28
+ end
29
+ js = identify_commands.join("\n") + "\n" + js
30
+ @commands = @commands.delete_if {|c| c[0] == :identify }
31
+
32
+ js
33
+ end
34
+
35
+ def track(*args)
36
+ "clicky.log(\"#{args.first}\");"
37
+ end
38
+
39
+ def identify(id, *args)
40
+ data = { :id=>id }.merge(args.first)
41
+ code = <<-HTML
42
+ <script type='text/javascript'>
43
+ var clicky_custom_session = #{data.to_json};
44
+ </script>
45
+ HTML
46
+ code
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ module Analytical
2
+ module Console
3
+ class Api
4
+ include Analytical::Base::Api
5
+ include ActionView::Helpers::JavaScriptHelper
6
+
7
+ def initialize(parent, options={})
8
+ super
9
+ @tracking_command_location = :head
10
+ end
11
+
12
+ def init_javascript(location)
13
+ return '' unless location==:head
14
+ js = <<-HTML
15
+ <!-- Analytical Init: Console -->
16
+ <script type="text/javascript">
17
+ console.log('Analytical Init: Console');
18
+ </script>
19
+ HTML
20
+ js
21
+ end
22
+
23
+ def track(*args)
24
+ "console.log(\"Analytical Track: #{escape_javascript args.to_json}\");"
25
+ end
26
+
27
+ def identify(id, *args)
28
+ "console.log(\"Analytical Identify: #{id} #{escape_javascript args.to_json}\");"
29
+ end
30
+
31
+ def event(name, *args)
32
+ "console.log(\"Analytical Event: #{name} #{escape_javascript args.to_json}\");"
33
+ end
34
+
35
+ end
36
+ end
37
+ end