analytical 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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