macros-garb 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,202 @@
1
+ garb
2
+ ====
3
+
4
+ by Tony Pitale with much help from Justin Marney, Patrick Reagan and others at Viget Labs
5
+
6
+ http://github.com/vigetlabs/garb
7
+
8
+ Important Changes
9
+ =================
10
+
11
+ Version 0.2.4 requires happymapper from rubygems, version 0.2.5. Be sure to update.
12
+
13
+ Version 0.2.0 makes major changes (compared to 0.1.0) to the way garb is used to build reports.
14
+ There is now both a module that gets included for generating defined classes,
15
+ as well as, slight changes to the way that the Report class can be used.
16
+
17
+ Description
18
+ -----------
19
+
20
+ Provides a Ruby API to the Google Analytics API.
21
+
22
+ http://code.google.com/apis/analytics/docs/gdata/gdataDeveloperGuide.html
23
+
24
+ Basic Usage
25
+ ===========
26
+
27
+ Login
28
+ -----
29
+
30
+ > Garb::Session.login(username, password)
31
+
32
+ Accounts
33
+ --------
34
+ > Garb::Account.all
35
+
36
+ Profiles
37
+ --------
38
+
39
+ > Garb::Account.first.profiles
40
+
41
+ > Garb::Profile.all
42
+ > profile = Garb::Profile.all.first
43
+
44
+ Define a Report Class and Get Results
45
+ -------------------------------------
46
+
47
+ class Exits
48
+ include Garb::Resource
49
+
50
+ metrics :exits, :pageviews, :exit_rate
51
+ dimensions :request_uri
52
+ end
53
+
54
+ Parameters
55
+ ----------
56
+
57
+ * start_date: The date of the period you would like this report to start
58
+ * end_date: The date to end, inclusive
59
+ * limit: The maximum number of results to be returned
60
+ * offset: The starting index
61
+
62
+ Metrics & Dimensions
63
+ --------------------
64
+
65
+ Metrics and Dimensions are very complex because of the ways in which the can and cannot be combined.
66
+
67
+ I suggest reading the google documentation to familiarize yourself with this.
68
+
69
+ http://code.google.com/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html#bounceRate
70
+
71
+ When you've returned, you can pass the appropriate combinations (up to 50 metrics and 2 dimenstions)
72
+ to garb, as an array, of symbols. Or you can simply push a symbol into the array.
73
+
74
+ Sorting
75
+ -------
76
+
77
+ Sorting can be done on any metric or dimension defined in the request, with .desc reversing the sort.
78
+
79
+ Building a Report
80
+ -----------------
81
+
82
+ Given the class, session, and profile from above we can do:
83
+
84
+ Exits.results(profile, :limit => 10, :offset => 19)
85
+
86
+ Or, with sorting and filters:
87
+
88
+ Exits.results(profile, :limit => 10, :offset => 19) do
89
+ filter :request_uri.contains => 'season', :exits.gt => 100
90
+ sort :exits
91
+ end
92
+
93
+ reports will be an array of OpenStructs with methods for the metrics and dimensions returned.
94
+
95
+ Build a One-Off Report
96
+ ----------------------
97
+
98
+ report = Garb::Report.new(profile)
99
+ report.metrics :pageviews
100
+ report.dimensions :request_uri
101
+
102
+ report.filter :request_uri.contains => 'season', :exits.gte => 10
103
+ report.sort :exits
104
+
105
+ report.results
106
+
107
+ Filtering
108
+ ---------
109
+
110
+ Google Analytics supports a significant number of filtering options.
111
+
112
+ http://code.google.com/apis/analytics/docs/gdata/gdataReference.html#filtering
113
+
114
+ We handle filtering as an array of hashes that you can push into,
115
+ which will be joined together (AND'd)
116
+
117
+ Here is what we can do currently:
118
+ (the operator is a method on a symbol metric or dimension)
119
+
120
+ Operators on metrics:
121
+
122
+ :eql => '==',
123
+ :not_eql => '!=',
124
+ :gt => '>',
125
+ :gte => '>=',
126
+ :lt => '<',
127
+ :lte => '<='
128
+
129
+ Operators on dimensions:
130
+
131
+ :matches => '==',
132
+ :does_not_match => '!=',
133
+ :contains => '=~',
134
+ :does_not_contain => '!~',
135
+ :substring => '=@',
136
+ :not_substring => '!@'
137
+
138
+ Given the previous example one-off report, we can add a line for filter:
139
+
140
+ report.filters << {:request_uri.eql => '/extend/effectively-using-git-with-subversion/'}
141
+
142
+ SSL
143
+ ---
144
+
145
+ Version 0.2.3 includes support for real ssl encryption for authentication. First do:
146
+
147
+ Garb::Session.login(username, password, :secure => true)
148
+
149
+ Next, be sure to download http://curl.haxx.se/ca/cacert.pem into your application somewhere.
150
+ Then, define a constant CA_CERT_FILE and point to that file.
151
+
152
+ For whatever reason, simply creating a new certificate store and setting the defaults would
153
+ not validate the google ssl certificate as authentic.
154
+
155
+ TODOS
156
+ -----
157
+
158
+ * Sessions are currently global, which isn't awesome
159
+ * Single user login is the only supported method currently.
160
+ Intend to add hooks for using OAuth
161
+ * Read opensearch header in results
162
+ * OR joining filter parameters
163
+
164
+ Requirements
165
+ ------------
166
+
167
+ happymapper >= 0.2.5 (should also install libxml)
168
+
169
+ Install
170
+ -------
171
+
172
+ sudo gem install garb
173
+
174
+ OR
175
+
176
+ sudo gem install vigetlabs-garb -s http://gems.github.com
177
+
178
+ License
179
+ -------
180
+
181
+ (The MIT License)
182
+
183
+ Copyright (c) 2008 Viget Labs
184
+
185
+ Permission is hereby granted, free of charge, to any person obtaining
186
+ a copy of this software and associated documentation files (the
187
+ 'Software'), to deal in the Software without restriction, including
188
+ without limitation the rights to use, copy, modify, merge, publish,
189
+ distribute, sublicense, and/or sell copies of the Software, and to
190
+ permit persons to whom the Software is furnished to do so, subject to
191
+ the following conditions:
192
+
193
+ The above copyright notice and this permission notice shall be
194
+ included in all copies or substantial portions of the Software.
195
+
196
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
197
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
198
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
199
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
200
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
201
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
202
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+
5
+ require 'lib/garb/version'
6
+
7
+ task :default => :test
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = 'garb'
11
+ s.version = Garb::Version.to_s
12
+ s.has_rdoc = false
13
+ s.summary = "Google Analytics API Ruby Wrapper"
14
+ s.authors = ['Tony Pitale','Justin Marney', 'Patrick Reagan']
15
+ s.email = 'tony.pitale@viget.com'
16
+ s.homepage = 'http://github.com/vigetlabs/garb'
17
+ s.files = %w(README.md Rakefile) + Dir.glob("lib/**/*")
18
+ s.test_files = Dir.glob("test/**/*")
19
+
20
+ s.add_dependency("happymapper", [">= 0.2.5"])
21
+ end
22
+
23
+ Rake::GemPackageTask.new(spec) do |pkg|
24
+ pkg.gem_spec = spec
25
+ end
26
+
27
+ Rake::TestTask.new do |t|
28
+ t.libs << 'test'
29
+ t.test_files = FileList["test/**/*_test.rb"]
30
+ t.verbose = true
31
+ end
32
+
33
+ desc 'Generate the gemspec to serve this Gem from Github'
34
+ task :github do
35
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
36
+ File.open(file, 'w') {|f| f << spec.to_ruby }
37
+ puts "Created gemspec: #{file}"
38
+ end
39
+
40
+ begin
41
+ require 'rcov/rcovtask'
42
+
43
+ desc "Generate RCov coverage report"
44
+ Rcov::RcovTask.new(:rcov) do |t|
45
+ t.test_files = FileList['test/**/*_test.rb']
46
+ t.rcov_opts << "-x lib/garb.rb -x lib/garb/version.rb"
47
+ end
48
+ rescue LoadError
49
+ nil
50
+ end
51
+
52
+ task :default => 'test'
53
+
54
+ # EOF
@@ -0,0 +1,16 @@
1
+ class Array
2
+ def group_to_array
3
+ h = Hash.new
4
+
5
+ each do |element|
6
+ key = yield(element)
7
+ if h.has_key?(key)
8
+ h[key] << element
9
+ else
10
+ h[key] = [element]
11
+ end
12
+ end
13
+
14
+ h.map{|k,v| v}
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # Concept from dm-core
2
+ class Operator
3
+ attr_reader :target, :operator, :prefix
4
+
5
+ def initialize(target, operator, prefix=false)
6
+ @target = target.to_ga
7
+ @operator = operator
8
+ @prefix = prefix
9
+ end
10
+
11
+ def to_ga
12
+ @prefix ? "#{operator}#{target}" : "#{target}#{operator}"
13
+ end
14
+
15
+ def ==(rhs)
16
+ target == rhs.target &&
17
+ operator == rhs.operator &&
18
+ prefix == rhs.prefix
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ class String
2
+ def underscored
3
+ self.gsub(/([A-Z])/, '_\1').downcase
4
+ end
5
+
6
+ def lower_camelized
7
+ self.gsub(/(_)(.)/) { $2.upcase }
8
+ end
9
+
10
+ def to_ga
11
+ "ga:#{self}"
12
+ end
13
+
14
+ def from_ga
15
+ self.gsub(/^ga\:/, '')
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ class Symbol
2
+ # OPERATORS
3
+
4
+ def self.operator(operators)
5
+ operators.each do |method, operator|
6
+ class_eval <<-CODE
7
+ def #{method}
8
+ Operator.new(self, '#{operator}')
9
+ end
10
+ CODE
11
+ end
12
+ end
13
+
14
+ # Sorting
15
+ def desc
16
+ Operator.new(self, '-', true)
17
+ end
18
+
19
+ operator :eql => '==',
20
+ :not_eql => '!=',
21
+ :gt => '>',
22
+ :gte => '>=',
23
+ :lt => '<',
24
+ :lte => '<=',
25
+ :matches => '==',
26
+ :does_not_match => '!=',
27
+ :contains => '=~',
28
+ :does_not_contain => '!~',
29
+ :substring => '=@',
30
+ :not_substring => '!@'
31
+
32
+ # Metric filters
33
+ def to_ga
34
+ "ga:#{self.to_s.lower_camelized}"
35
+ end
36
+ end
data/lib/garb.rb ADDED
@@ -0,0 +1,71 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'rubygems'
6
+ require 'cgi'
7
+ require 'ostruct'
8
+ require 'happymapper'
9
+
10
+ require 'garb/version'
11
+ require 'garb/authentication_request'
12
+ require 'garb/data_request'
13
+ require 'garb/session'
14
+ require 'garb/profile'
15
+ require 'garb/account'
16
+ require 'garb/report_parameter'
17
+ require 'garb/report_response'
18
+ require 'garb/resource'
19
+ require 'garb/report'
20
+
21
+ require 'extensions/string'
22
+ require 'extensions/operator'
23
+ require 'extensions/symbol'
24
+ require 'extensions/array'
25
+
26
+ module Garb
27
+ # :stopdoc:
28
+ GA = "http://schemas.google.com/analytics/2008"
29
+
30
+ VERSION = '0.1.2'
31
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
32
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
33
+ # :startdoc:
34
+
35
+ # Returns the version string for the library.
36
+ #
37
+ def self.version
38
+ VERSION
39
+ end
40
+
41
+ # Returns the library path for the module. If any arguments are given,
42
+ # they will be joined to the end of the libray path using
43
+ # <tt>File.join</tt>.
44
+ #
45
+ def self.libpath( *args )
46
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
47
+ end
48
+
49
+ # Returns the lpath for the module. If any arguments are given,
50
+ # they will be joined to the end of the path using
51
+ # <tt>File.join</tt>.
52
+ #
53
+ def self.path( *args )
54
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
55
+ end
56
+
57
+ # Utility method used to rquire all files ending in .rb that lie in the
58
+ # directory below this file that has the same name as the filename passed
59
+ # in. Optionally, a specific _directory_ name can be passed in such that
60
+ # the _filename_ does not have to be equivalent to the directory.
61
+ #
62
+ def self.require_all_libs_relative_to( fname, dir = nil )
63
+ dir ||= ::File.basename(fname, '.*')
64
+ search_me = ::File.expand_path(
65
+ ::File.join(::File.dirname(fname), dir, '*', '*.rb'))
66
+
67
+ Dir.glob(search_me).sort.each {|rb| require rb}
68
+ end
69
+ end # module Garb
70
+
71
+ # EOF
@@ -0,0 +1,15 @@
1
+ module Garb
2
+ class Account
3
+ attr_reader :id, :name, :profiles
4
+
5
+ def initialize(profiles)
6
+ @id = profiles.first.account_id
7
+ @name = profiles.first.account_name
8
+ @profiles = profiles
9
+ end
10
+
11
+ def self.all
12
+ Profile.all.group_to_array{|p| p.account_id}.map{|profiles| new(profiles)}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ module Garb
2
+ class AuthenticationRequest
3
+ class AuthError < StandardError;end
4
+
5
+ URL = 'https://www.google.com/accounts/ClientLogin'
6
+
7
+ def initialize(email, password)
8
+ @email = email
9
+ @password = password
10
+ end
11
+
12
+ def parameters
13
+ {
14
+ 'Email' => @email,
15
+ 'Passwd' => @password,
16
+ 'accountType' => 'HOSTED_OR_GOOGLE',
17
+ 'service' => 'analytics',
18
+ 'source' => 'vigetLabs-garb-001'
19
+ }
20
+ end
21
+
22
+ def uri
23
+ URI.parse(URL)
24
+ end
25
+
26
+ def send_request(ssl_mode)
27
+ http = Net::HTTP.new(uri.host, uri.port)
28
+ http.use_ssl = true
29
+ http.verify_mode = ssl_mode
30
+
31
+ if ssl_mode == OpenSSL::SSL::VERIFY_PEER
32
+ http.ca_file = CA_CERT_FILE
33
+ end
34
+
35
+ http.request(build_request) do |response|
36
+ raise AuthError unless response.is_a?(Net::HTTPOK)
37
+ end
38
+ end
39
+
40
+ def build_request
41
+ post = Net::HTTP::Post.new(uri.path)
42
+ post.set_form_data(parameters)
43
+ post
44
+ end
45
+
46
+ def auth_token(opts={})
47
+ ssl_mode = opts[:secure] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
48
+ send_request(ssl_mode).body.match(/^Auth=(.*)$/)[1]
49
+ end
50
+
51
+ end
52
+ end