analytical 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/analytical.gemspec +124 -0
- data/example/.gitignore +4 -0
- data/example/Gemfile +27 -0
- data/example/README +244 -0
- data/example/Rakefile +10 -0
- data/example/app/controllers/application_controller.rb +6 -0
- data/example/app/controllers/page_controller.rb +11 -0
- data/example/app/helpers/application_helper.rb +2 -0
- data/example/app/helpers/page_helper.rb +2 -0
- data/example/app/views/layouts/application.html.erb +17 -0
- data/example/app/views/page/index.html.erb +5 -0
- data/example/app/views/page/test_a.html.erb +10 -0
- data/example/app/views/page/test_b.html.erb +6 -0
- data/example/config.ru +4 -0
- data/example/config/analytical.yml +6 -0
- data/example/config/application.rb +46 -0
- data/example/config/boot.rb +6 -0
- data/example/config/database.yml +22 -0
- data/example/config/environment.rb +5 -0
- data/example/config/environments/development.rb +19 -0
- data/example/config/environments/production.rb +42 -0
- data/example/config/environments/test.rb +32 -0
- data/example/config/initializers/backtrace_silencers.rb +7 -0
- data/example/config/initializers/inflections.rb +10 -0
- data/example/config/initializers/mime_types.rb +5 -0
- data/example/config/initializers/secret_token.rb +7 -0
- data/example/config/initializers/session_store.rb +8 -0
- data/example/config/locales/en.yml +5 -0
- data/example/config/routes.rb +64 -0
- data/example/db/seeds.rb +7 -0
- data/example/doc/README_FOR_APP +2 -0
- data/example/lib/tasks/.gitkeep +0 -0
- data/example/public/404.html +26 -0
- data/example/public/422.html +26 -0
- data/example/public/500.html +26 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/images/rails.png +0 -0
- data/example/public/javascripts/application.js +2 -0
- data/example/public/javascripts/controls.js +965 -0
- data/example/public/javascripts/dragdrop.js +974 -0
- data/example/public/javascripts/effects.js +1123 -0
- data/example/public/javascripts/prototype.js +4874 -0
- data/example/public/javascripts/rails.js +118 -0
- data/example/public/robots.txt +5 -0
- data/example/public/stylesheets/.gitkeep +0 -0
- data/example/script/rails +9 -0
- data/example/test/functional/page_controller_test.rb +19 -0
- data/example/test/performance/browsing_test.rb +9 -0
- data/example/test/test_helper.rb +13 -0
- data/example/test/unit/helpers/page_helper_test.rb +4 -0
- data/example/vendor/plugins/.gitkeep +0 -0
- data/lib/analytical.rb +52 -0
- data/lib/analytical/api.rb +90 -0
- data/lib/analytical/base.rb +34 -0
- data/lib/analytical/clicky.rb +51 -0
- data/lib/analytical/console.rb +37 -0
- data/lib/analytical/google.rb +34 -0
- data/lib/analytical/kiss_metrics.rb +35 -0
- data/rails/init.rb +1 -0
- data/spec/analytical/api_spec.rb +122 -0
- data/spec/analytical/clicky_spec.rb +49 -0
- data/spec/analytical/google_spec.rb +38 -0
- data/spec/analytical/kiss_metrics_spec.rb +44 -0
- data/spec/analytical_spec.rb +64 -0
- data/spec/config/analytical.yml +6 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +22 -0
- 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
|
+
});
|
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,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
|
File without changes
|
data/lib/analytical.rb
ADDED
@@ -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
|