garb 0.5.1 → 0.6.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.
data/README.md CHANGED
@@ -1,37 +1,12 @@
1
- garb
1
+ Garb
2
2
  ====
3
3
 
4
- by Tony Pitale with much help from Justin Marney, Patrick Reagan and others at Viget Labs
5
-
6
4
  http://github.com/vigetlabs/garb
7
5
 
8
6
  Important Changes
9
7
  =================
10
8
 
11
- Version 0.5.0
12
-
13
- * Filters now have a new DSL so that I could toss Symbol operators (which conflict with DataMapper and MongoMapper)
14
- * The method of passing a hash to filters no longer works, at all
15
-
16
- Version 0.4.0
17
-
18
- * Changes the api for filters and sort making it consistent with metrics/dimensions
19
- * If you wish to clear the defaults defined on a class, you may use clear_(filters/sort/metrics/dimensions)
20
- * To make a custom class using Garb::Resource, you must now extend instead of include the module
21
-
22
- Version 0.3.2
23
-
24
- * adds Profile.first which can be used to get the first profile with a table id, or web property id (UA number)
25
-
26
- Version 0.2.4
27
-
28
- * requires happymapper from rubygems, version 0.2.5. Be sure to update.
29
-
30
- Version 0.2.0
31
-
32
- * makes major changes (compared to 0.1.0) to the way garb is used to build reports.
33
- * There is now both a module that gets included for generating defined classes,
34
- * slight changes to the way that the Report class can be used.
9
+ Please read CHANGELOG
35
10
 
36
11
  Description
37
12
  -----------
@@ -43,13 +18,19 @@ Description
43
18
  Basic Usage
44
19
  ===========
45
20
 
46
- Login
47
- -----
21
+ Single User Login
22
+ -----------------
48
23
 
49
24
  > Garb::Session.login(username, password)
25
+
26
+ OAuth Access Token
27
+ ------------------
28
+
29
+ > Garb::Session.access_token = access_token # assign from oauth gem
50
30
 
51
31
  Accounts
52
32
  --------
33
+
53
34
  > Garb::Account.all
54
35
 
55
36
  Profiles
@@ -62,8 +43,8 @@ Profiles
62
43
  > Garb::Profile.all
63
44
  > profile = Garb::Profile.all.first
64
45
 
65
- Define a Report Class and Get Results
66
- -------------------------------------
46
+ Define a Report Class
47
+ ---------------------
67
48
 
68
49
  class Exits
69
50
  extend Garb::Resource
@@ -75,8 +56,20 @@ Define a Report Class and Get Results
75
56
  filters do
76
57
  eql(:page_path, 'season')
77
58
  end
59
+
60
+ # alternative:
61
+ # filters :page_path.eql => 10
78
62
  end
79
63
 
64
+ Get the Results
65
+ ---------------
66
+
67
+ > Exits.results(profile)
68
+
69
+ OR shorthand
70
+
71
+ > profile.exits
72
+
80
73
  Other Parameters
81
74
  ----------------
82
75
 
@@ -118,6 +111,9 @@ Building a Report
118
111
  contains(:page_path, 'season')
119
112
  gt(:exits, 100)
120
113
  end
114
+
115
+ # or with a hash
116
+ # filters :page_path.contains => 'season', :exits.gt => 100
121
117
  end
122
118
 
123
119
  reports will be an array of OpenStructs with methods for the metrics and dimensions returned.
@@ -135,6 +131,9 @@ Build a One-Off Report
135
131
  gte(:exits, 10)
136
132
  and
137
133
 
134
+ # or with a hash
135
+ # report.filters :page_path.contains => 'season', :exits.gt => 100
136
+
138
137
  report.results
139
138
 
140
139
  Filtering
@@ -152,21 +151,21 @@ Filtering
152
151
 
153
152
  Operators on metrics:
154
153
 
155
- :eql => '==',
156
- :not_eql => '!=',
157
- :gt => '>',
158
- :gte => '>=',
159
- :lt => '<',
160
- :lte => '<='
154
+ eql => '==',
155
+ not_eql => '!=',
156
+ gt => '>',
157
+ gte => '>=',
158
+ lt => '<',
159
+ lte => '<='
161
160
 
162
161
  Operators on dimensions:
163
162
 
164
- :matches => '==',
165
- :does_not_match => '!=',
166
- :contains => '=~',
167
- :does_not_contain => '!~',
168
- :substring => '=@',
169
- :not_substring => '!@'
163
+ matches => '==',
164
+ does_not_match => '!=',
165
+ contains => '=~',
166
+ does_not_contain => '!~',
167
+ substring => '=@',
168
+ not_substring => '!@'
170
169
 
171
170
  Given the previous example one-off report, we can add a line for filter:
172
171
 
@@ -174,10 +173,14 @@ Filtering
174
173
  eql(:page_path, '/extend/effectively-using-git-with-subversion/')
175
174
  end
176
175
 
176
+ Or, if you're comfortable using symbol operators:
177
+
178
+ report.filters :page_path.eql => '/extend/effectively-using-git-with-subversion/'
179
+
177
180
  SSL
178
181
  ---
179
182
 
180
- Version 0.2.3 includes support for real ssl encryption for authentication. First do:
183
+ Version 0.2.3 includes support for real ssl encryption for SINGLE USER authentication. First do:
181
184
 
182
185
  Garb::Session.login(username, password, :secure => true)
183
186
 
@@ -191,27 +194,40 @@ TODOS
191
194
  -----
192
195
 
193
196
  * Sessions are currently global, which isn't awesome
194
- * Single user login is the only supported method currently.
195
- Intend to add hooks for using OAuth
196
197
  * Read opensearch header in results
197
198
 
198
199
  Requirements
199
200
  ------------
200
201
 
201
- happymapper >= 0.3.0 (should also install libxml)
202
- active_support >= 2.3.0
202
+ * happymapper >= 0.3.0 (should also install libxml)
203
+ * active_support >= 2.3.0
204
+
205
+ Requirements for Testing
206
+ ------------------------
207
+
208
+ * jferris-mocha
209
+ * tpitale-shoulda (works with minitest)
203
210
 
204
211
  Install
205
212
  -------
206
213
 
207
214
  sudo gem install garb
208
215
 
216
+ Contributors
217
+ ------------
218
+
219
+ Many Thanks, for all their help, goes to:
220
+
221
+ * Patrick Reagan
222
+ * Justin Marney
223
+ * Nick Plante
224
+
209
225
  License
210
226
  -------
211
227
 
212
228
  (The MIT License)
213
229
 
214
- Copyright (c) 2008 Viget Labs
230
+ Copyright (c) 2010 Viget Labs
215
231
 
216
232
  Permission is hereby granted, free of charge, to any person obtaining
217
233
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ spec = Gem::Specification.new do |s|
12
12
  s.has_rdoc = false
13
13
  s.rubyforge_project = 'viget'
14
14
  s.summary = "Google Analytics API Ruby Wrapper"
15
- s.authors = ['Tony Pitale', 'Patrick Reagan']
15
+ s.authors = ['Tony Pitale']
16
16
  s.email = 'tony.pitale@viget.com'
17
17
  s.homepage = 'http://github.com/vigetlabs/garb'
18
18
  s.files = %w(README.md Rakefile) + Dir.glob("lib/**/*")
data/lib/garb.rb CHANGED
@@ -9,10 +9,10 @@ require 'happymapper'
9
9
  require 'active_support'
10
10
 
11
11
  require 'garb/version'
12
- require 'garb/profile_array'
13
12
  require 'garb/authentication_request'
14
13
  require 'garb/data_request'
15
14
  require 'garb/session'
15
+ require 'garb/profile_reports'
16
16
  require 'garb/profile'
17
17
  require 'garb/account'
18
18
  require 'garb/filter_parameters'
data/lib/garb/account.rb CHANGED
@@ -9,7 +9,21 @@ module Garb
9
9
  end
10
10
 
11
11
  def self.all
12
- Profile.all.group_to_array{|p| p.account_id}.map{|profiles| new(profiles)}
12
+ # Profile.all.group_to_array{|p| p.account_id}.map{|profiles| new(profiles)}
13
+
14
+ profile_groups = Profile.all.inject({}) do |hash, profile|
15
+ key = profile.account_id
16
+
17
+ if hash.has_key?(key)
18
+ hash[key] << profile
19
+ else
20
+ hash[key] = [profile]
21
+ end
22
+
23
+ hash
24
+ end
25
+
26
+ profile_groups.map {|k,v| v}.map {|profiles| new(profiles)}
13
27
  end
14
28
  end
15
29
  end
@@ -1,5 +1,6 @@
1
1
  module Garb
2
2
  class DataRequest
3
+ class ClientError < StandardError; end
3
4
 
4
5
  def initialize(base_url, parameters={})
5
6
  @base_url = base_url
@@ -16,13 +17,25 @@ module Garb
16
17
  end
17
18
 
18
19
  def send_request
20
+ response = if Session.single_user?
21
+ single_user_request
22
+ elsif Session.oauth_user?
23
+ oauth_user_request
24
+ end
25
+
26
+ raise ClientError, response.body.inspect unless response.kind_of?(Net::HTTPSuccess)
27
+ response
28
+ end
29
+
30
+ def single_user_request
19
31
  http = Net::HTTP.new(uri.host, uri.port)
20
32
  http.use_ssl = true
21
33
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
22
- response = http.get("#{uri.path}#{query_string}", 'Authorization' => "GoogleLogin auth=#{Session.auth_token}")
23
- raise response.body.inspect unless response.is_a?(Net::HTTPOK)
24
- response
34
+ http.get("#{uri.path}#{query_string}", 'Authorization' => "GoogleLogin auth=#{Session.auth_token}")
25
35
  end
26
36
 
37
+ def oauth_user_request
38
+ Session.access_token.get("#{uri}#{query_string}")
39
+ end
27
40
  end
28
- end
41
+ end
data/lib/garb/profile.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  module Garb
2
2
  class Profile
3
-
3
+
4
+ include ProfileReports
5
+
4
6
  attr_reader :table_id, :title, :account_name, :account_id, :web_property_id
5
7
 
6
8
  class Property
@@ -44,8 +46,7 @@ module Garb
44
46
  def self.all
45
47
  url = "https://www.google.com/analytics/feeds/accounts/default"
46
48
  response = DataRequest.new(url).send_request
47
- profiles = Entry.parse(response.body).map {|entry| new(entry)}
48
- ProfileArray.new(profiles)
49
+ Entry.parse(response.body).map {|entry| new(entry)}
49
50
  end
50
51
 
51
52
  def self.first(id)
@@ -0,0 +1,15 @@
1
+ module Garb
2
+ module ProfileReports
3
+ def self.add_report_method(klass)
4
+ # demodulize leaves potential to redefine
5
+ # these methods given different namespaces
6
+ method_name = klass.to_s.demodulize.underscore
7
+
8
+ class_eval <<-CODE
9
+ def #{method_name}(opts = {}, &block)
10
+ #{klass}.results(self, opts, &block)
11
+ end
12
+ CODE
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ require 'reports/exits'
2
+ require 'reports/visits'
3
+ require 'reports/bounces'
4
+ require 'reports/pageviews'
5
+ require 'reports/unique_pageviews'
@@ -0,0 +1,5 @@
1
+ class Bounces
2
+ extend Garb::Resource
3
+
4
+ metric :bounces
5
+ end
@@ -0,0 +1,5 @@
1
+ class Exits
2
+ extend Garb::Resource
3
+
4
+ metric :exits
5
+ end
@@ -0,0 +1,5 @@
1
+ class Pageviews
2
+ extend Garb::Resource
3
+
4
+ metric :pageviews
5
+ end
@@ -0,0 +1,5 @@
1
+ class UniquePageviews
2
+ extend Garb::Resource
3
+
4
+ metric :unique_pageviews
5
+ end
@@ -0,0 +1,5 @@
1
+ class Visits
2
+ extend Garb::Resource
3
+
4
+ metric :visits
5
+ end
data/lib/garb/resource.rb CHANGED
@@ -3,6 +3,15 @@ module Garb
3
3
  MONTH = 2592000
4
4
  URL = "https://www.google.com/analytics/feeds/data"
5
5
 
6
+ def self.extended(base)
7
+ # define a method on a module that gets included into profile
8
+ # Exits would make:
9
+ # to enable profile.exits(options_hash, &block)
10
+ # returns Exits.results(self, options_hash, &block)
11
+ # every class defined which extends Resource will add to the module
12
+ ProfileReports.add_report_method(base)
13
+ end
14
+
6
15
  %w(metrics dimensions sort).each do |parameter|
7
16
  class_eval <<-CODE
8
17
  def #{parameter}(*fields)
data/lib/garb/session.rb CHANGED
@@ -2,12 +2,21 @@ module Garb
2
2
  module Session
3
3
  extend self
4
4
 
5
- attr_accessor :auth_token, :email
6
-
5
+ attr_accessor :auth_token, :access_token, :email
6
+
7
+ # use only for single user authentication
7
8
  def login(email, password, opts={})
8
9
  self.email = email
9
10
  auth_request = AuthenticationRequest.new(email, password, opts)
10
11
  self.auth_token = auth_request.auth_token(opts)
11
12
  end
13
+
14
+ def single_user?
15
+ auth_token && auth_token.is_a?(String)
16
+ end
17
+
18
+ def oauth_user?
19
+ !access_token.nil?
20
+ end
12
21
  end
13
22
  end
data/lib/garb/version.rb CHANGED
@@ -2,8 +2,8 @@ module Garb
2
2
  module Version
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 5
6
- TINY = 1
5
+ MINOR = 6
6
+ TINY = 0
7
7
 
8
8
  def self.to_s # :nodoc:
9
9
  [MAJOR, MINOR, TINY].join('.')
@@ -6,10 +6,14 @@ module Garb
6
6
  should "have an array of accounts with all profiles" do
7
7
  p1 = stub(:account_id => '1111', :account_name => 'Blog 1')
8
8
  p2 = stub(:account_id => '1112', :account_name => 'Blog 2')
9
- Profile.stubs(:all).returns(ProfileArray.new([p1,p2,p1,p2]))
10
- Account.expects(:new).with([p1,p1]).returns('account1')
11
- Account.expects(:new).with([p2,p2]).returns('account2')
9
+
10
+ Profile.stubs(:all).returns([p1,p2,p1,p2])
11
+ Account.stubs(:new).returns('account1', 'account2')
12
+
12
13
  assert_equal ['account1','account2'], Account.all
14
+ assert_received(Profile, :all)
15
+ assert_received(Account, :new) {|e| e.with([p1,p1])}
16
+ assert_received(Account, :new) {|e| e.with([p2,p2])}
13
17
  end
14
18
  end
15
19
 
@@ -30,23 +30,73 @@ module Garb
30
30
  assert_equal expected, DataRequest.new(url).uri
31
31
  end
32
32
 
33
- should "be able to make a request to the GAAPI" do
33
+ should "be able to send a request for a single user" do
34
+ Session.stubs(:single_user?).returns(true)
35
+ response = mock('Net::HTTPOK') do |m|
36
+ m.expects(:kind_of?).with(Net::HTTPSuccess).returns(true)
37
+ end
38
+
39
+ data_request = DataRequest.new('https://example.com/data', 'key' => 'value')
40
+ data_request.stubs(:single_user_request).returns(response)
41
+ data_request.send_request
42
+
43
+ assert_received(data_request, :single_user_request)
44
+ end
45
+
46
+ should "be able to send a request for an oauth user" do
47
+ Session.stubs(:single_user?).returns(false)
48
+ Session.stubs(:oauth_user?).returns(true)
49
+ response = mock('Net::HTTPOK') do |m|
50
+ m.expects(:kind_of?).with(Net::HTTPSuccess).returns(true)
51
+ end
52
+
53
+ data_request = DataRequest.new('https://example.com/data', 'key' => 'value')
54
+ data_request.stubs(:oauth_user_request).returns(response)
55
+ data_request.send_request
56
+
57
+ assert_received(data_request, :oauth_user_request)
58
+ end
59
+
60
+ should "raise if the request is unauthorized" do
61
+ Session.stubs(:single_user?).returns(false)
62
+ Session.stubs(:oauth_user?).returns(true)
63
+ response = mock('Net::HTTPUnauthorized', :body => 'Error')
64
+
65
+ data_request = DataRequest.new('https://example.com/data', 'key' => 'value')
66
+ data_request.stubs(:oauth_user_request).returns(response)
67
+
68
+ assert_raises(Garb::DataRequest::ClientError) do
69
+ data_request.send_request
70
+ end
71
+ end
72
+
73
+ should "be able to request via the ouath access token" do
74
+ access_token = stub(:get => "responseobject")
75
+ Session.stubs(:access_token).returns(access_token)
76
+
77
+ data_request = DataRequest.new('https://example.com/data', 'key' => 'value')
78
+ assert_equal 'responseobject', data_request.oauth_user_request
79
+
80
+ assert_received(Session, :access_token)
81
+ assert_received(access_token, :get) {|e| e.with('https://example.com/data?key=value')}
82
+ end
83
+
84
+ should "be able to request via http with an auth token" do
34
85
  Session.expects(:auth_token).with().returns('toke')
35
86
  response = mock
36
- response.expects(:is_a?).with(Net::HTTPOK).returns(true)
37
-
87
+
38
88
  http = mock do |m|
39
89
  m.expects(:use_ssl=).with(true)
40
90
  m.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
41
91
  m.expects(:get).with('/data?key=value', 'Authorization' => 'GoogleLogin auth=toke').returns(response)
42
92
  end
43
-
93
+
44
94
  Net::HTTP.expects(:new).with('example.com', 443).returns(http)
45
-
95
+
46
96
  data_request = DataRequest.new('https://example.com/data', 'key' => 'value')
47
- assert_equal response, data_request.send_request
97
+ assert_equal response, data_request.single_user_request
48
98
  end
49
99
  end
50
100
 
51
101
  end
52
- end
102
+ end
@@ -0,0 +1,29 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '/test_helper')
2
+
3
+ module Garb
4
+
5
+ Exits = Class.new
6
+
7
+ class FakeProfile
8
+ include ProfileReports
9
+ end
10
+
11
+ class ProfileReportsTest < MiniTest::Unit::TestCase
12
+ context "The ProfileReports module" do
13
+ should "define a new method when given a class" do
14
+ ProfileReports.add_report_method(Exits)
15
+ assert_equal true, FakeProfile.new.respond_to?(:exits)
16
+ end
17
+
18
+ should "return results from the given class with options" do
19
+ results = [1,2,3]
20
+ Exits.stubs(:results).returns(results)
21
+ ProfileReports.add_report_method(Exits)
22
+
23
+ profile = FakeProfile.new
24
+ assert_equal results, profile.exits(:start => "now")
25
+ assert_received(Exits, :results) {|e| e.with(profile, :start => "now")}
26
+ end
27
+ end
28
+ end
29
+ end
@@ -28,7 +28,16 @@ module Garb
28
28
  Session.login('email', 'password')
29
29
  assert_equal 'email', Session.email
30
30
  end
31
-
31
+
32
+ should "know if the Session is for a single user" do
33
+ Session.auth_token = "abcdefg1234567"
34
+ assert_equal true, Session.single_user?
35
+ end
36
+
37
+ should "know if the Session is for oauth" do
38
+ Session.access_token = 'some_oauth_access_token'
39
+ assert_equal true, Session.oauth_user?
40
+ end
32
41
  end
33
42
 
34
43
  end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: garb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Pitale
8
- - Patrick Reagan
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
11
 
13
- date: 2010-01-14 00:00:00 -05:00
12
+ date: 2010-02-10 00:00:00 -05:00
14
13
  default_executable:
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
@@ -48,12 +47,17 @@ files:
48
47
  - lib/garb/authentication_request.rb
49
48
  - lib/garb/data_request.rb
50
49
  - lib/garb/filter_parameters.rb
51
- - lib/garb/oauth_session.rb
52
50
  - lib/garb/profile.rb
53
- - lib/garb/profile_array.rb
51
+ - lib/garb/profile_reports.rb
54
52
  - lib/garb/report.rb
55
53
  - lib/garb/report_parameter.rb
56
54
  - lib/garb/report_response.rb
55
+ - lib/garb/reports/bounces.rb
56
+ - lib/garb/reports/exits.rb
57
+ - lib/garb/reports/pageviews.rb
58
+ - lib/garb/reports/unique_pageviews.rb
59
+ - lib/garb/reports/visits.rb
60
+ - lib/garb/reports.rb
57
61
  - lib/garb/resource.rb
58
62
  - lib/garb/session.rb
59
63
  - lib/garb/version.rb
@@ -68,6 +72,7 @@ files:
68
72
  - test/unit/garb/data_request_test.rb
69
73
  - test/unit/garb/filter_parameters_test.rb
70
74
  - test/unit/garb/oauth_session_test.rb
75
+ - test/unit/garb/profile_reports_test.rb
71
76
  - test/unit/garb/profile_test.rb
72
77
  - test/unit/garb/report_parameter_test.rb
73
78
  - test/unit/garb/report_response_test.rb
@@ -114,6 +119,7 @@ test_files:
114
119
  - test/unit/garb/data_request_test.rb
115
120
  - test/unit/garb/filter_parameters_test.rb
116
121
  - test/unit/garb/oauth_session_test.rb
122
+ - test/unit/garb/profile_reports_test.rb
117
123
  - test/unit/garb/profile_test.rb
118
124
  - test/unit/garb/report_parameter_test.rb
119
125
  - test/unit/garb/report_response_test.rb
@@ -1,21 +0,0 @@
1
- module Garb
2
- class OAuthSession
3
- attr_accessor :access_token
4
-
5
- OAuthGetRequestToken = "https://www.google.com/accounts/OAuthGetRequestToken"
6
- OAuthAuthorizeToken = "https://www.google.com/accounts/OAuthAuthorizeToken"
7
- OAuthGetAccessToken = "https://www.google.com/accounts/OAuthGetAccessToken"
8
-
9
- def get_request_token
10
-
11
- end
12
-
13
- def authorization_request
14
-
15
- end
16
-
17
- def get_access_token
18
-
19
- end
20
- end
21
- end
@@ -1,18 +0,0 @@
1
- module Garb
2
- class ProfileArray < Array
3
- def group_to_array
4
- h = Hash.new
5
-
6
- each do |element|
7
- key = yield(element)
8
- if h.has_key?(key)
9
- h[key] << element
10
- else
11
- h[key] = [element]
12
- end
13
- end
14
-
15
- h.map{|k,v| v}
16
- end
17
- end
18
- end