gattica 0.4.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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ github-test.rb
3
+ examples/active_example.rb
4
+ examples/.DS_Store
@@ -0,0 +1,42 @@
1
+ == 0.4.0
2
+ * er1c added start_index and max_results
3
+ * er1c added paging for all results
4
+ * er1c added get_to_csv to "stream" saving results to a file IO
5
+ * thieso2 fix DataPoint when GA-Path includec colons
6
+ * jeremyf Added ability to parse addition query strings
7
+ * nedski Added support for proxy via env var
8
+
9
+ == 0.3.4
10
+ * rumble updated the regex used to pull apart the filters so it didn't get confused when there a filename, for example, started with a /
11
+
12
+ == 0.3.2
13
+ * er1c updated to use standard Ruby CSV library
14
+
15
+ == 0.3.0
16
+ * Support for filters (filters are all AND'ed together, no OR yet)
17
+
18
+ == 0.2.1
19
+ * More robust error checking on HTTP calls
20
+ * Added to_xml to get raw XML output from Google
21
+
22
+ == 0.2.0 / 2009-04-27
23
+ * Changed initialization format: pass a hash of options rather than individual email, password and profile_id
24
+ * Can initialize with a valid token and use that instead of requiring email/password each time
25
+ * Can initialize with your own logger object instead of having to use the default (useful if you're using with Rails, initialize with RAILS_DEFAULT_LOGGER)
26
+ * Show error if token is invalid or expired (Google returns a 401 on any HTTP call)
27
+ * Started tests
28
+
29
+ == 0.1.4 / 2009-04-22
30
+ * Another attempt at getting the gem to build on github
31
+
32
+ == 0.1.3 / 2009-04-22
33
+ * Getting gem to build on github
34
+
35
+ == 0.1.2 / 2009-04-22
36
+ * Updated readme and examples, better documentation throughout
37
+
38
+ == 0.1.1 / 2009-04-22
39
+ * When outputting as CSV, surround each piece of data with double quotes (appears pretty common for various properties (like Browser name) to contain commas
40
+
41
+ == 0.1.0 / 2009-03-26
42
+ * Basic functionality working good. Can't use filters yet.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2009 Rob Cameron
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,190 @@
1
+ Gattica is a Ruby library for talking to the Google Analytics API.
2
+
3
+ = Installation
4
+ Install the gattica gem using github as the source:
5
+
6
+ gem install cannikin-gattica -s http://gems.github.com
7
+
8
+ When you want to require, you just use 'gattica' as the gem name:
9
+
10
+ require 'rubygems'
11
+ require 'gattica'
12
+
13
+ = Introduction
14
+ It's a good idea to familiarize yourself with the Google API docs: http://code.google.com/apis/analytics/docs/gdata/gdataDeveloperGuide.html
15
+
16
+ In particular there are some very specific combinations of Metrics and Dimensions that
17
+ you are restricted to and those are explained in this document: http://code.google.com/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html
18
+
19
+ See examples/example.rb for some code that should work automatically if you replace the email/password with your own
20
+
21
+ There are generally three steps to getting info from the GA API:
22
+
23
+ 1. Authenticate
24
+ 2. Get a profile id
25
+ 3. Get the data you really want
26
+
27
+ = Usage
28
+ This library does all three. A typical transaction will look like this:
29
+
30
+ gs = Gattica.new({:email => 'johndoe@google.com', :password => 'password', :profile_id => 123456})
31
+ results = gs.get({ :start_date => '2008-01-01',
32
+ :end_date => '2008-02-01',
33
+ :dimensions => 'browser',
34
+ :metrics => 'pageviews',
35
+ :sort => '-pageviews'})
36
+
37
+ So we instantiate a copy of Gattica and pass it a Google Account email address and password.
38
+ The third parameter is the profile_id that we want to access data for.
39
+
40
+ Then we call +get+ with the parameters we want to shape our data with. In this case we want
41
+ total page views, broken down by browser, from Jan 1 2008 to Feb 1 2008, sorted by descending
42
+ page views. If you wanted to sort pageviews ascending, just leave off the minus.
43
+
44
+ If you don't know the profile_id you want to get data for, call +accounts+
45
+
46
+ gs = Gattica.new({:email => 'johndoe@google.com', :password => 'password'})
47
+ accounts = gs.accounts
48
+
49
+ This returns all of the accounts and profiles that the user has access to. Note that if you
50
+ use this method to get profiles, you need to manually set the profile before you can call +get+
51
+
52
+ gs.profile_id = 123456
53
+ results = gs.get({ :start_date => '2008-01-01',
54
+ :end_date => '2008-02-01',
55
+ :dimensions => 'browser',
56
+ :metrics => 'pageviews',
57
+ :sort => '-pageviews'})
58
+
59
+ When you put in the names for the dimensions and metrics you want, refer to this doc for the
60
+ available names: http://code.google.com/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html
61
+
62
+ Note that you do *not* use the 'ga:' prefix when you tell Gattica which ones you want. Gattica
63
+ adds that for you automatically.
64
+
65
+ If you want to search on more than one dimension or metric, pass them in as an array (you can
66
+ also pass in single values as arrays too, if you wish):
67
+
68
+ results = gs.get({ :start_date => '2008-01-01',
69
+ :end_date => '2008-02-01',
70
+ :dimensions => ['browser','browserVersion'],
71
+ :metrics => ['pageviews','visits'],
72
+ :sort => ['-pageviews']})
73
+
74
+ == Filters
75
+ Filters can be pretty complex as far as GA is concerned. You can filter on either dimensions or metrics
76
+ or both. And then your filters can be ANDed together or they can ORed together. There are also rules,
77
+ which are not immediately apparent, about what you can filter and how.
78
+
79
+ By default filters passed to a +get+ are ANDed together. This means that all filters need to match for
80
+ the result to be returned.
81
+
82
+ results = gs.get({:start_date => '2008-01-01',
83
+ :end_date => '2008-02-01',
84
+ :dimensions => ['browser','browserVersion'],
85
+ :metrics => ['pageviews','visits'],
86
+ :sort => ['-pageviews'],
87
+ :filters => ['browser == Firefox','pageviews >= 10000']})
88
+
89
+ This says "return only results where the 'browser' dimension contains the word 'Firefox' and the
90
+ 'pageviews' metric is greater than or equal to 10,000.
91
+
92
+ Filters can contain spaces around the operators, or not. These two lines are equivalent (I think
93
+ the spaces make the filter more readable):
94
+
95
+ :filters => ['browser == Firefox','pageviews >= 10000']
96
+
97
+ :filters => ['browser==Firefox','pageviews>=10000']
98
+
99
+ Once again, do _not_ include the +ga:+ prefix before the dimension/metric you're filtering against.
100
+ Gattica will add this automatically.
101
+
102
+ You will probably find that as you try different filters, GA will report that some of them aren't
103
+ valid. I haven't found any documentation that says which filter combinations are valid in what
104
+ circumstances, so I suppose it's just trial and error at this point.
105
+
106
+ For more on filtering syntax, see the Analytics API docs: http://code.google.com/apis/analytics/docs/gdata/gdataReference.html#filtering
107
+
108
+ = Output
109
+ When Gattica was originally created it was intended to take the data returned and put it into
110
+ Excel for someone else to crunch through the numbers. Thus, Gattica has great built-in support
111
+ for CSV output. Once you have your data simply:
112
+
113
+ results.to_csv
114
+
115
+ A couple example rows of what that looks like:
116
+
117
+ "id","updated","title","browser","pageviews"
118
+ "http://www.google.com/analytics/feeds/data?ids=ga:12345&ga:browser=Internet%20Explorer&start-date=2009-01-01&end-date=2009-01-31","2009-01-30T16:00:00-08:00","ga:browser=Internet Explorer","Internet Explorer","53303"
119
+ "http://www.google.com/analytics/feeds/data?ids=ga:12345&ga:browser=Firefox&start-date=2009-01-01&end-date=2009-01-31","2009-01-30T16:00:00-08:00","ga:browser=Firefox","Firefox","20323"
120
+
121
+ Data is comma-separated and double-quote delimited. In most cases, people don't care
122
+ about the id, updated, or title attributes of this data. They just want the dimensions and
123
+ metrics. In that case, pass the symbol +:short+ to +to_csv+ and receive get back only the
124
+ the good stuff:
125
+
126
+ results.to_csv(:short)
127
+
128
+ Which returns:
129
+
130
+ "browser","pageviews"
131
+ "Internet Explorer","53303"
132
+ "Firefox","20323"
133
+
134
+ You can also just output the results as a string and you'll get the standard inspect syntax:
135
+
136
+ results.to_s
137
+
138
+ Gives you:
139
+
140
+ { "end_date"=>#<Date: 4909725/2,0,2299161>,
141
+ "start_date"=>#<Date: 4909665/2,0,2299161>,
142
+ "points"=>[
143
+ { "title"=>"ga:browser=Internet Explorer",
144
+ "dimensions"=>[{:browser=>"Internet Explorer"}],
145
+ "id"=>"http://www.google.com/analytics/feeds/data?ids=ga:12345&amp;ga:browser=Internet%20Explorer&amp;start-date=2009-01-01&amp;end-date=2009-01-31",
146
+ "metrics"=>[{:pageviews=>53303}],
147
+ "updated"=>#<DateTime: 212100120000001/86400000,-1/3,2299161>}]}
148
+
149
+ == Notes on Authentication
150
+ === Authentication Token
151
+ Google recommends not re-authenticating each time you do a request against the API. To accomplish
152
+ this you should save the authorization token you receive from Google and use that for future
153
+ requests:
154
+
155
+ ga.token => 'DSasdf94...' (some huge long string)
156
+
157
+ You can now initialize Gattica with this token for future requests:
158
+
159
+ ga = Gattica.new({:token => 'DSasdf94...'})
160
+
161
+ (You enter the full token, of course). I'm not sure how long a token from the Google's ClientLogin
162
+ system remains active, but if/when I do I'll add that to the docs here.
163
+
164
+ === Headers
165
+ Google expects a special header in all HTTP requests called 'Authorization'. This contains your
166
+ token:
167
+
168
+ Authorization = GoogleLogin auth=DSasdf94...
169
+
170
+ This header is generated automatically. If you have your own headers you'd like to add, you can
171
+ pass them in when you initialize:
172
+
173
+ ga = Gattica.new({:token => 'DSasdf94...', :headers => {'My-Special-Header':'my_custom_value'}})
174
+
175
+ And they'll be sent with every request you make.
176
+
177
+ = Limitations
178
+ The GA API limits each call to 1000 results per "page." If you want more, you need to tell
179
+ the API what number to begin at and it will return the next 1000. Gattica does not currently
180
+ support this, but it's in the plan for the very next version.
181
+
182
+ Currently all filters you supply are ANDed together before being sent to GA. Support for ORing
183
+ is coming soon.
184
+
185
+ = The Future
186
+ A couple of things I have planned:
187
+
188
+ 1. Tests!
189
+ 2. The option to use a custom delimiter for output
190
+
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "gattica"
9
+ gemspec.summary = "Gattica is a Ruby library for extracting data from the Google Analytics API."
10
+ gemspec.email = "cannikinn@gmail.com"
11
+ gemspec.homepage = "http://github.com/cannikin/gattica"
12
+ gemspec.description = "Gattica is a Ruby library for extracting data from the Google Analytics API."
13
+ gemspec.authors = ["Rob Cameron"]
14
+ gemspec.add_dependency('hpricot','>=0.6.164')
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ Rake::TestTask.new do |t|
21
+ t.libs << 'lib'
22
+ t.pattern = 'test/**/test_*.rb'
23
+ t.verbose = false
24
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 4
4
+ :patch: 0
@@ -0,0 +1,42 @@
1
+ require '../lib/gattica'
2
+
3
+ # authenticate with the API via email/password
4
+ ga = Gattica.new({:email => 'username@gmail.com', :password => 'password'})
5
+
6
+ # or, initialize via a pre-existing token. This initialization does not authenticate immediately,
7
+ # but will throw an error on subsequent calls (like ga.accounts) if the token is invalid
8
+ # ga = Gattica.new({:token => 'DQAAAJYAAACN-JMelka5I0Fs-T6lF53eUSfUooeHgcKc1iEdc0wkDS3w8GaXY7LjuUB_4vmzDB94HpScrULiweW_xQsU8yyUgdInDIX7ZnHm8_o0knf6FWSR90IoAZGsphpqteOjZ3O0NlNt603GgG7ylvGWRSeHl1ybD38nysMsKJR-dj0LYgIyPMvtgXLrqr_20oTTEExYbrDSg5_q84PkoLHUcODZ' })
9
+
10
+ # get the list of accounts you have access to with that username and password
11
+ accounts = ga.accounts
12
+
13
+ # for this example we just use the first account's profile_id, but you'll probably want to look
14
+ # at this list and choose the profile_id of the account you want (the web_property_id is the
15
+ # property you're most used to seeing in GA, looks like UA123456-1)
16
+ ga.profile_id = accounts.first.profile_id
17
+
18
+ # If you're using Gattica with a web app you'll want to save the authorization token
19
+ # and use that on subsequent requests (Google recommends not re-authenticating each time)
20
+ # ga.token
21
+
22
+ # now get the number of page views by browser for Janurary 2009
23
+ data = ga.get({ :start_date => '2009-01-01',
24
+ :end_date => '2009-01-31',
25
+ :dimensions => ['browser'],
26
+ :metrics => ['pageviews'],
27
+ :sort => ['-pageviews'] })
28
+
29
+ # output the data as CSV
30
+ puts data.to_csv
31
+
32
+ # a little more complex example with filtering. Show all pageviews by Firefox browsers
33
+ # (any version) where the number of page views is greater than 100
34
+ data = ga.get({ :start_date => '2009-01-01',
35
+ :end_date => '2009-01-31',
36
+ :dimensions => ['browser','browserVersion'],
37
+ :metrics => ['pageviews'],
38
+ :sort => ['-pageviews'],
39
+ :filters => ['browser == Firefox', 'pageviews > 100'] })
40
+
41
+ # write the data out as CSV in "short" format (doesn't include id, updated or title parameters)
42
+ puts data.to_csv(:short)
@@ -0,0 +1,72 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{gattica}
8
+ s.version = "0.4.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Rob Cameron"]
12
+ s.date = %q{2009-09-04}
13
+ s.description = %q{Gattica is a Ruby library for extracting data from the Google Analytics API.}
14
+ s.email = %q{cannikinn@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "History.txt",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION.yml",
26
+ "examples/example.rb",
27
+ "gattica.gemspec",
28
+ "lib/gattica.rb",
29
+ "lib/gattica/account.rb",
30
+ "lib/gattica/auth.rb",
31
+ "lib/gattica/convertible.rb",
32
+ "lib/gattica/core_extensions.rb",
33
+ "lib/gattica/data_point.rb",
34
+ "lib/gattica/data_set.rb",
35
+ "lib/gattica/exceptions.rb",
36
+ "lib/gattica/user.rb",
37
+ "test/helper.rb",
38
+ "test/suite.rb",
39
+ "test/test_auth.rb",
40
+ "test/test_engine.rb",
41
+ "test/test_gattica.rb",
42
+ "test/test_user.rb"
43
+ ]
44
+ s.has_rdoc = true
45
+ s.homepage = %q{http://github.com/cannikin/gattica}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.1}
49
+ s.summary = %q{Gattica is a Ruby library for extracting data from the Google Analytics API.}
50
+ s.test_files = [
51
+ "test/helper.rb",
52
+ "test/suite.rb",
53
+ "test/test_auth.rb",
54
+ "test/test_engine.rb",
55
+ "test/test_gattica.rb",
56
+ "test/test_user.rb",
57
+ "examples/example.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 2
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ s.add_runtime_dependency(%q<hpricot>, [">= 0.6.164"])
66
+ else
67
+ s.add_dependency(%q<hpricot>, [">= 0.6.164"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<hpricot>, [">= 0.6.164"])
71
+ end
72
+ end
@@ -0,0 +1,348 @@
1
+ $:.unshift File.dirname(__FILE__) # for use/testing when no gem is installed
2
+
3
+ # external
4
+ require 'net/http'
5
+ require 'net/https'
6
+ require 'uri'
7
+ require 'cgi'
8
+ require 'logger'
9
+ require 'rubygems'
10
+ require 'hpricot'
11
+ require 'yaml'
12
+
13
+ # internal
14
+ require 'gattica/core_extensions'
15
+ require 'gattica/convertible'
16
+ require 'gattica/exceptions'
17
+ require 'gattica/user'
18
+ require 'gattica/auth'
19
+ require 'gattica/account'
20
+ require 'gattica/data_set'
21
+ require 'gattica/data_point'
22
+
23
+ # Gattica is a Ruby library for talking to the Google Analytics API.
24
+ #
25
+ # Please see the README for usage docs.
26
+
27
+ module Gattica
28
+
29
+ VERSION = '0.4.0'
30
+
31
+ # Creates a new instance of Gattica::Engine and gets us going. Please see the README for usage docs.
32
+ #
33
+ # ga = Gattica.new({:email => 'anonymous@anon.com', :password => 'password, :profile_id => 123456 })
34
+
35
+ def self.new(*args)
36
+ Engine.new(*args)
37
+ end
38
+
39
+ # The real meat of Gattica, deals with talking to GA, returning and parsing results. You actually get
40
+ # an instance of this when you go Gattica.new()
41
+
42
+ class Engine
43
+
44
+ SERVER = 'www.google.com'
45
+ PORT = 443
46
+ SECURE = true
47
+ DEFAULT_ARGS = { :start_date => nil, :end_date => nil, :dimensions => [], :metrics => [], :filters => [], :sort => [], :start_index => 1, :max_results => 10000, :page => false }
48
+ DEFAULT_OPTIONS = { :email => nil, :password => nil, :token => nil, :profile_id => nil, :debug => false, :headers => {}, :logger => Logger.new(STDOUT) }
49
+ FILTER_METRIC_OPERATORS = %w{ == != > < >= <= }
50
+ FILTER_DIMENSION_OPERATORS = %w{ == != =~ !~ =@ ~@ }
51
+
52
+ attr_reader :user
53
+ attr_accessor :profile_id, :token
54
+
55
+ # Create a user, and get them authorized.
56
+ # If you're making a web app you're going to want to save the token that's retrieved by Gattica
57
+ # so that you can use it later (Google recommends not re-authenticating the user for each and every request)
58
+ #
59
+ # ga = Gattica.new({:email => 'johndoe@google.com', :password => 'password', :profile_id => 123456})
60
+ # ga.token => 'DW9N00wenl23R0...' (really long string)
61
+ #
62
+ # Or if you already have the token (because you authenticated previously and now want to reuse that session):
63
+ #
64
+ # ga = Gattica.new({:token => '23ohda09hw...', :profile_id => 123456})
65
+
66
+ def initialize(options={})
67
+ @options = DEFAULT_OPTIONS.merge(options)
68
+ @logger = @options[:logger]
69
+
70
+ @profile_id = @options[:profile_id] # if you don't include the profile_id now, you'll have to set it manually later via Gattica::Engine#profile_id=
71
+ @user_accounts = nil # filled in later if the user ever calls Gattica::Engine#accounts
72
+ @headers = {}.merge(@options[:headers]) # headers used for any HTTP requests (Google requires a special 'Authorization' header which is set any time @token is set)
73
+
74
+ # save a proxy-aware http connection for everyone to use
75
+ proxy_host = nil
76
+ proxy_port = nil
77
+ proxy_var = SECURE ? 'https_proxy' : 'http_proxy'
78
+ [proxy_var, proxy_var.upcase].each do |pxy|
79
+ if ENV[pxy]
80
+ uri = URI::parse(ENV[pxy])
81
+ proxy_host = uri.host
82
+ proxy_port = uri.port
83
+ end
84
+ end
85
+ @http = Net::HTTP::Proxy(proxy_host,proxy_port).new(SERVER, PORT)
86
+ @http.use_ssl = SECURE
87
+ @http.set_debug_output $stdout if @options[:debug]
88
+
89
+ # authenticate
90
+ if @options[:email] && @options[:password] # email and password: authenticate, get a token from Google's ClientLogin, save it for later
91
+ @user = User.new(@options[:email], @options[:password])
92
+ @auth = Auth.new(@http, user)
93
+ self.token = @auth.tokens[:auth]
94
+ elsif @options[:token] # use an existing token
95
+ self.token = @options[:token]
96
+ else # no login or token, you can't do anything
97
+ raise GatticaError::NoLoginOrToken, 'You must provide an email and password, or authentication token'
98
+ end
99
+
100
+ # TODO: check that the user has access to the specified profile and show an error here rather than wait for Google to respond with a message
101
+ end
102
+
103
+
104
+ # Returns the list of accounts the user has access to. A user may have multiple accounts on Google Analytics
105
+ # and each account may have multiple profiles. You need the profile_id in order to get info from GA. If you
106
+ # don't know the profile_id then use this method to get a list of all them. Then set the profile_id of your
107
+ # instance and you can make regular calls from then on.
108
+ #
109
+ # ga = Gattica.new({:email => 'johndoe@google.com', :password => 'password'})
110
+ # ga.get_accounts
111
+ # # you parse through the accounts to find the profile_id you need
112
+ # ga.profile_id = 12345678
113
+ # # now you can perform a regular search, see Gattica::Engine#get
114
+ #
115
+ # If you pass in a profile id when you instantiate Gattica::Search then you won't need to
116
+ # get the accounts and find a profile_id - you apparently already know it!
117
+ #
118
+ # See Gattica::Engine#get to see how to get some data.
119
+
120
+ def accounts
121
+ # if we haven't retrieved the user's accounts yet, get them now and save them
122
+ if @user_accounts.nil?
123
+ data = do_http_get('/analytics/feeds/accounts/default')
124
+ xml = Hpricot(data)
125
+ @user_accounts = xml.search(:entry).collect { |entry| Account.new(entry) }
126
+ end
127
+ return @user_accounts
128
+ end
129
+
130
+ # Performs a Gattica::Engine#get but instead of returning the dataset streams it to the file handle in a CSV format
131
+ #
132
+ # == Usage
133
+ #
134
+ # gs = Gattica.new({:email => 'johndoe@google.com', :password => 'password', :profile_id => 123456})
135
+ # fh = File.new("file.csv", "w")
136
+ # gs.get_to_csv({ :start_date => '2008-01-01',
137
+ # :end_date => '2008-02-01',
138
+ # :dimensions => 'browser',
139
+ # :metrics => 'pageviews',
140
+ # :sort => 'pageviews',
141
+ # :filters => ['browser == Firefox']}, fh, :short)
142
+ #
143
+ # See Gattica::Engine#get to see details of arguments
144
+
145
+ def get_to_csv(args={}, fh = nil, format = :long)
146
+ raise GatticaError::InvalidFileType, "Invalid file handle" unless !fh.nil?
147
+ results(args, fh, :csv, format)
148
+ end
149
+
150
+ # This is the method that performs the actual request to get data.
151
+ #
152
+ # == Usage
153
+ #
154
+ # gs = Gattica.new({:email => 'johndoe@google.com', :password => 'password', :profile_id => 123456})
155
+ # gs.get({ :start_date => '2008-01-01',
156
+ # :end_date => '2008-02-01',
157
+ # :dimensions => 'browser',
158
+ # :metrics => 'pageviews',
159
+ # :sort => 'pageviews',
160
+ # :filters => ['browser == Firefox']})
161
+ #
162
+ # == Input
163
+ #
164
+ # When calling +get+ you'll pass in a hash of options. For a description of what these mean to
165
+ # Google Analytics, see http://code.google.com/apis/analytics/docs
166
+ #
167
+ # Required values are:
168
+ #
169
+ # * +start_date+ => Beginning of the date range to search within
170
+ # * +end_date+ => End of the date range to search within
171
+ #
172
+ # Optional values are:
173
+ #
174
+ # * +dimensions+ => an array of GA dimensions (without the ga: prefix)
175
+ # * +metrics+ => an array of GA metrics (without the ga: prefix)
176
+ # * +filter+ => an array of GA dimensions/metrics you want to filter by (without the ga: prefix)
177
+ # * +sort+ => an array of GA dimensions/metrics you want to sort by (without the ga: prefix)
178
+ # * +page+ => true|false Does the paging to create a single set of all of the data
179
+ # * +start_index+ => Beginning offset of the query (default 1)
180
+ # * +max_results+ => How many results to grab (maximum 10,000)
181
+ #
182
+ # == Exceptions
183
+ #
184
+ # If a user doesn't have access to the +profile_id+ you specified, you'll receive an error.
185
+ # Likewise, if you attempt to access a dimension or metric that doesn't exist, you'll get an
186
+ # error back from Google Analytics telling you so.
187
+
188
+ def get(args={})
189
+ return results(args)
190
+ end
191
+
192
+ private
193
+
194
+ def results(args={}, fh=nil, type=nil, format=nil)
195
+ raise GatticaError::InvalidFileType, "Invalid file type" unless type.nil? ||[:csv,:xml].include?(type)
196
+ args = validate_and_clean(DEFAULT_ARGS.merge(args))
197
+
198
+ header = 0
199
+ results = nil
200
+ total_results = args[:max_results]
201
+ while(args[:start_index] < total_results)
202
+ query_string = build_query_string(args,@profile_id)
203
+ @logger.debug("Query String: " + query_string) if @debug
204
+
205
+ data = do_http_get("/analytics/feeds/data?#{query_string}")
206
+ result = DataSet.new(Hpricot.XML(data))
207
+
208
+ #handle returning results
209
+ results.points.concat(result.points) if !results.nil? && fh.nil?
210
+ #handle csv
211
+
212
+ if(!fh.nil? && type == :csv && header == 0)
213
+ fh.write result.to_csv_header(format)
214
+ header = 1
215
+ end
216
+
217
+ fh.write result.to_csv(:noheader) if !fh.nil? && type == :csv
218
+ fh.flush if !fh.nil?
219
+
220
+ results = result if results.nil?
221
+ total_results = result.total_results
222
+ args[:start_index] += args[:max_results]
223
+ break if !args[:page] # only continue while if we are suppose to page
224
+ end
225
+ return results if fh.nil?
226
+ end
227
+
228
+ # Since google wants the token to appear in any HTTP call's header, we have to set that header
229
+ # again any time @token is changed so we override the default writer (note that you need to set
230
+ # @token with self.token= instead of @token=)
231
+
232
+ def token=(token)
233
+ @token = token
234
+ set_http_headers
235
+ end
236
+
237
+
238
+ # Does the work of making HTTP calls and then going through a suite of tests on the response to make
239
+ # sure it's valid and not an error
240
+
241
+ def do_http_get(query_string)
242
+ response, data = @http.get(query_string, @headers)
243
+
244
+ # error checking
245
+ if response.code != '200'
246
+ case response.code
247
+ when '400'
248
+ raise GatticaError::AnalyticsError, response.body + " (status code: #{response.code})"
249
+ when '401'
250
+ raise GatticaError::InvalidToken, "Your authorization token is invalid or has expired (status code: #{response.code})"
251
+ else # some other unknown error
252
+ raise GatticaError::UnknownAnalyticsError, response.body + " (status code: #{response.code})"
253
+ end
254
+ end
255
+
256
+ return data
257
+ end
258
+
259
+ private
260
+
261
+ # Sets up the HTTP headers that Google expects (this is called any time @token is set either by Gattica
262
+ # or manually by the user since the header must include the token)
263
+ def set_http_headers
264
+ @headers['Authorization'] = "GoogleLogin auth=#{@token}"
265
+ end
266
+
267
+
268
+ # Creates a valid query string for GA
269
+ def build_query_string(args,profile)
270
+ query_params = args.clone
271
+ ga_start_date = query_params.delete(:start_date)
272
+ ga_end_date = query_params.delete(:end_date)
273
+ ga_dimensions = query_params.delete(:dimensions)
274
+ ga_metrics = query_params.delete(:metrics)
275
+ ga_sort = query_params.delete(:sort)
276
+ ga_filters = query_params.delete(:filters)
277
+
278
+ output = "ids=ga:#{profile}&start-date=#{ga_start_date}&end-date=#{ga_end_date}"
279
+ unless ga_dimensions.nil? || ga_dimensions.empty?
280
+ output += '&dimensions=' + ga_dimensions.collect do |dimension|
281
+ "ga:#{dimension}"
282
+ end.join(',')
283
+ end
284
+ unless ga_metrics.nil? || ga_metrics.empty?
285
+ output += '&metrics=' + ga_metrics.collect do |metric|
286
+ "ga:#{metric}"
287
+ end.join(',')
288
+ end
289
+ unless ga_sort.nil? || ga_sort.empty?
290
+ output += '&sort=' + Array(ga_sort).collect do |sort|
291
+ sort[0..0] == '-' ? "-ga:#{sort[1..-1]}" : "ga:#{sort}" # if the first character is a dash, move it before the ga:
292
+ end.join(',')
293
+ end
294
+
295
+ # TODO: update so that in regular expression filters (=~ and !~), any initial special characters in the regular expression aren't also picked up as part of the operator (doesn't cause a problem, but just feels dirty)
296
+ unless args[:filters].empty? # filters are a little more complicated because they can have all kinds of modifiers
297
+ output += '&filters=' + args[:filters].collect do |filter|
298
+ match, name, operator, expression = *filter.match(/^(\w*)\s*([=!<>~@]*)\s*(.*)$/) # splat the resulting Match object to pull out the parts automatically
299
+ unless name.empty? || operator.empty? || expression.empty? # make sure they all contain something
300
+ "ga:#{name}#{CGI::escape(operator.gsub(/ /,''))}#{CGI::escape(expression)}" # remove any whitespace from the operator before output
301
+ else
302
+ raise GatticaError::InvalidFilter, "The filter '#{filter}' is invalid. Filters should look like 'browser == Firefox' or 'browser==Firefox'"
303
+ end
304
+ end.join(';')
305
+ end
306
+
307
+ query_params.inject(output) {|m,(key,value)| m << "&#{key}=#{value}"}
308
+
309
+ return output
310
+ end
311
+
312
+
313
+ # Validates that the args passed to +get+ are valid
314
+ def validate_and_clean(args)
315
+
316
+ raise GatticaError::MissingStartDate, ':start_date is required' if args[:start_date].nil? || args[:start_date].empty?
317
+ raise GatticaError::MissingEndDate, ':end_date is required' if args[:end_date].nil? || args[:end_date].empty?
318
+ raise GatticaError::TooManyDimensions, 'You can only have a maximum of 7 dimensions' if args[:dimensions] && (args[:dimensions].is_a?(Array) && args[:dimensions].length > 7)
319
+ raise GatticaError::TooManyMetrics, 'You can only have a maximum of 10 metrics' if args[:metrics] && (args[:metrics].is_a?(Array) && args[:metrics].length > 10)
320
+
321
+ possible = args[:dimensions] + args[:metrics]
322
+
323
+ # make sure that the user is only trying to sort fields that they've previously included with dimensions and metrics
324
+ if args[:sort]
325
+ missing = args[:sort].find_all do |arg|
326
+ !possible.include? arg.gsub(/^-/,'') # remove possible minuses from any sort params
327
+ end
328
+ unless missing.empty?
329
+ raise GatticaError::InvalidSort, "You are trying to sort by fields that are not in the available dimensions or metrics: #{missing.join(', ')}"
330
+ end
331
+ end
332
+
333
+ # make sure that the user is only trying to filter fields that are in dimensions or metrics
334
+ if args[:filters]
335
+ missing = args[:filters].find_all do |arg|
336
+ !possible.include? arg.match(/^\w*/).to_s # get the name of the filter and compare
337
+ end
338
+ unless missing.empty?
339
+ raise GatticaError::InvalidSort, "You are trying to filter by fields that are not in the available dimensions or metrics: #{missing.join(', ')}"
340
+ end
341
+ end
342
+
343
+ return args
344
+ end
345
+
346
+
347
+ end
348
+ end
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+
4
+ module Gattica
5
+
6
+ # Represents an account that an authenticated user has access to
7
+
8
+ class Account
9
+
10
+ include Convertible
11
+
12
+ attr_reader :id, :updated, :title, :table_id, :account_id, :account_name, :profile_id, :web_property_id
13
+
14
+ def initialize(xml)
15
+ @id = xml.at(:id).inner_html
16
+ @updated = DateTime.parse(xml.at(:updated).inner_html)
17
+ @title = xml.at(:title).inner_html
18
+ @table_id = xml.at('dxp:tableid').inner_html
19
+ @account_id = xml.at("dxp:property[@name='ga:accountId']").attributes['value'].to_i
20
+ @account_name = xml.at("dxp:property[@name='ga:accountName']").attributes['value']
21
+ @profile_id = xml.at("dxp:property[@name='ga:profileId']").attributes['value'].to_i
22
+ @web_property_id = xml.at("dxp:property[@name='ga:webPropertyId']").attributes['value']
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,52 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+
4
+ module Gattica
5
+
6
+ # Authenticates a user against the Google Client Login system
7
+
8
+ class Auth
9
+
10
+ include Convertible
11
+
12
+ SCRIPT_NAME = '/accounts/ClientLogin'
13
+ HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded', 'User-Agent' => 'Ruby Net::HTTP' } # Google asks that you be nice and provide a user-agent string
14
+ OPTIONS = { :source => 'gattica-'+VERSION, :service => 'analytics' } # Google asks that you provide the name of your app as a 'source' parameter in your POST
15
+
16
+ attr_reader :tokens
17
+
18
+ # Try to authenticate the user
19
+ def initialize(http, user)
20
+ options = OPTIONS.merge(user.to_h)
21
+
22
+ response, data = http.post(SCRIPT_NAME, options.to_query, HEADERS)
23
+ if response.code != '200'
24
+ case response.code
25
+ when '403'
26
+ raise GatticaError::CouldNotAuthenticate, 'Your email and/or password is not recognized by the Google ClientLogin system (status code: 403)'
27
+ else
28
+ raise GatticaError::UnknownAnalyticsError, response.body + " (status code: #{response.code})"
29
+ end
30
+ end
31
+ @tokens = parse_tokens(data)
32
+ end
33
+
34
+
35
+ private
36
+
37
+ # Parse the authentication tokens out of the response and makes them available as a hash
38
+ #
39
+ # tokens[:auth] => Google requires this for every request (added to HTTP headers on GET requests)
40
+ # tokens[:sid] => Not used
41
+ # tokens[:lsid] => Not used
42
+
43
+ def parse_tokens(data)
44
+ tokens = {}
45
+ data.split("\n").each do |t|
46
+ tokens.merge!({ t.split('=').first.downcase.to_sym => t.split('=').last })
47
+ end
48
+ return tokens
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,39 @@
1
+ module Gattica
2
+
3
+ # Common output methods that are sharable
4
+
5
+ module Convertible
6
+
7
+ # output as hash
8
+ def to_h
9
+ output = {}
10
+ instance_variables.each do |var|
11
+ output.merge!({ var[1..-1] => instance_variable_get(var) }) unless var == '@xml' # exclude the whole XML dump
12
+ end
13
+ output
14
+ end
15
+
16
+ # output nice inspect syntax
17
+ def to_s
18
+ to_h.inspect
19
+ end
20
+
21
+ alias inspect to_s
22
+
23
+ def to_query
24
+ to_h.to_query
25
+ end
26
+
27
+ # Return the raw XML (if the object has a @xml instance variable, otherwise convert the object itself to xml)
28
+ def to_xml
29
+ if @xml
30
+ @xml
31
+ else
32
+ self.to_xml
33
+ end
34
+ end
35
+
36
+ alias to_yml to_yaml
37
+
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ class Hash
2
+
3
+ def to_query
4
+ require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
5
+ self.collect do |key, value|
6
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
7
+ end.sort * '&'
8
+ end
9
+
10
+ def key
11
+ self.keys.first if self.length == 1
12
+ end
13
+
14
+ def value
15
+ self.values.first if self.length == 1
16
+ end
17
+
18
+ def stringify_keys
19
+ inject({}) do |options, (key, value)|
20
+ options[key.to_s] = value
21
+ options
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,60 @@
1
+ require "csv"
2
+
3
+ module Gattica
4
+
5
+ # Represents a single "row" of data containing any number of dimensions, metrics
6
+
7
+ class DataPoint
8
+
9
+ include Convertible
10
+
11
+ attr_reader :id, :updated, :title, :dimensions, :metrics, :xml
12
+
13
+ # Parses the XML <entry> element
14
+ def initialize(xml)
15
+ @xml = xml.to_s
16
+ @id = xml.at('id').inner_html
17
+ @updated = DateTime.parse(xml.at('updated').inner_html)
18
+ @title = xml.at('title').inner_html
19
+ @dimensions = xml.search('dxp:dimension').collect do |dimension|
20
+ { dimension.attributes['name'].split(':').last.to_sym => dimension.attributes['value'].split(':', 1).last }
21
+ end
22
+ @metrics = xml.search('dxp:metric').collect do |metric|
23
+ { metric.attributes['name'].split(':').last.to_sym => metric.attributes['value'].split(':', 1).last.to_i }
24
+ end
25
+ end
26
+
27
+
28
+ # Outputs in Comma Seperated Values format
29
+ def to_csv(format = :long)
30
+ output = ''
31
+
32
+ columns = []
33
+ # only output
34
+ case format
35
+ when :long
36
+ columns.concat([@id, @updated, @title])
37
+ end
38
+
39
+ # output all dimensions
40
+ columns.concat(@dimensions.map {|d| d.value})
41
+
42
+ # output all metrics
43
+ columns.concat(@metrics.map {|m| m.value})
44
+
45
+ output = CSV.generate_line(columns)
46
+ return output
47
+ end
48
+
49
+
50
+ def to_yaml
51
+ { 'id' => @id,
52
+ 'updated' => @updated,
53
+ 'title' => @title,
54
+ 'dimensions' => @dimensions,
55
+ 'metrics' => @metrics }.to_yaml
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,76 @@
1
+ module Gattica
2
+
3
+ # Encapsulates the data returned by the GA API
4
+
5
+ class DataSet
6
+
7
+ include Convertible
8
+
9
+ attr_reader :total_results, :start_index, :items_per_page, :start_date, :end_date, :points, :xml
10
+
11
+ def initialize(xml)
12
+ @xml = xml.to_s
13
+ @total_results = xml.at('openSearch:totalResults').inner_html.to_i
14
+ @start_index = xml.at('openSearch:startIndex').inner_html.to_i
15
+ @items_per_page = xml.at('openSearch:itemsPerPage').inner_html.to_i
16
+ @start_date = Date.parse(xml.at('dxp:startDate').inner_html)
17
+ @end_date = Date.parse(xml.at('dxp:endDate').inner_html)
18
+ @points = xml.search(:entry).collect { |entry| DataPoint.new(entry) }
19
+ end
20
+
21
+ def to_csv_header(format = :long)
22
+ # build the headers
23
+ output = ''
24
+ columns = []
25
+
26
+ # only show the nitty gritty details of id, updated_at and title if requested
27
+ case format #it would be nice if case statements in ruby worked differently
28
+ when :long
29
+ columns.concat(["id", "updated", "title"])
30
+ unless @points.empty? # if there was at least one result
31
+ columns.concat(@points.first.dimensions.map {|d| d.key})
32
+ columns.concat(@points.first.metrics.map {|m| m.key})
33
+ end
34
+ when :short
35
+ unless @points.empty? # if there was at least one result
36
+ columns.concat(@points.first.dimensions.map {|d| d.key})
37
+ columns.concat(@points.first.metrics.map {|m| m.key})
38
+ end
39
+ when :noheader
40
+ end
41
+
42
+ output = CSV.generate_line(columns) + "\n" if (columns.size > 0)
43
+
44
+ return output
45
+ end
46
+
47
+ # output important data to CSV, ignoring all the specific data about this dataset
48
+ # (total_results, start_date) and just output the data from the points
49
+
50
+ def to_csv(format = :long)
51
+ output = ''
52
+
53
+ # build the headers
54
+ output = to_csv_header(format)
55
+
56
+ # get the data from each point
57
+ @points.each do |point|
58
+ output += point.to_csv(format) + "\n"
59
+ end
60
+
61
+ return output
62
+ end
63
+
64
+
65
+ def to_yaml
66
+ { 'total_results' => @total_results,
67
+ 'start_index' => @start_index,
68
+ 'items_per_page' => @items_per_page,
69
+ 'start_date' => @start_date,
70
+ 'end_date' => @end_date,
71
+ 'points' => @points}.to_yaml
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,23 @@
1
+ module GatticaError
2
+ # usage errors
3
+ class InvalidFileType < StandardError; end;
4
+ # user errors
5
+ class InvalidEmail < StandardError; end;
6
+ class InvalidPassword < StandardError; end;
7
+ # authentication errors
8
+ class CouldNotAuthenticate < StandardError; end;
9
+ class NoLoginOrToken < StandardError; end;
10
+ class InvalidToken < StandardError; end;
11
+ # profile errors
12
+ class InvalidProfileId < StandardError; end;
13
+ # search errors
14
+ class TooManyDimensions < StandardError; end;
15
+ class TooManyMetrics < StandardError; end;
16
+ class InvalidSort < StandardError; end;
17
+ class InvalidFilter < StandardError; end;
18
+ class MissingStartDate < StandardError; end;
19
+ class MissingEndDate < StandardError; end;
20
+ # errors from Analytics
21
+ class AnalyticsError < StandardError; end;
22
+ class UnknownAnalyticsError < StandardError; end;
23
+ end
@@ -0,0 +1,31 @@
1
+ module Gattica
2
+
3
+ # Represents a user to be authenticated by GA
4
+
5
+ class User
6
+
7
+ include Convertible
8
+
9
+ attr_accessor :email, :password
10
+
11
+ def initialize(email,password)
12
+ @email = email
13
+ @password = password
14
+ validate
15
+ end
16
+
17
+ # User gets a special +to_h+ because Google expects +Email+ and +Passwd+ instead of our nicer internal names
18
+ def to_h
19
+ { :Email => @email,
20
+ :Passwd => @password }
21
+ end
22
+
23
+ private
24
+ # Determine whether or not this is a valid user
25
+ def validate
26
+ raise GatticaError::InvalidEmail, "The email address '#{@email}' is not valid" if not @email.match(/^(?:[_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-zA-Z0-9\-\.]+)*(\.[a-z]{2,4})$/i)
27
+ raise GatticaError::InvalidPassword, "The password cannot be blank" if @password.empty? || @password.nil?
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. lib gattica])
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'mocha'
6
+
7
+ # include Gattica
8
+
9
+ def fixture(name)
10
+ File.read(File.join(File.dirname(__FILE__), 'fixtures', name))
11
+ end
12
+
13
+ def absolute_project_path
14
+ File.expand_path(File.join(File.dirname(__FILE__), '..'))
15
+ end
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+
3
+ tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"]
4
+ tests.each do |file|
5
+ require file
6
+ end
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestAuth < Test::Unit::TestCase
4
+ def setup
5
+
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+
12
+ end
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestEngine < Test::Unit::TestCase
4
+ def setup
5
+
6
+ end
7
+
8
+ def test_initialization
9
+ # assert Gattica.new({:email => 'anonymous@anon.com', :password => 'none'}) # you can initialize with a potentially invalid email and password
10
+ assert Gattica.new({:token => 'test'}) # you can initialize with a potentially invalid token
11
+ assert_raise GatticaError::NoLoginOrToken do Gattica.new() end # but, you must initialize with one or the other
12
+ end
13
+
14
+ end
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestUser < Test::Unit::TestCase
4
+ def test_build_query_string
5
+ @gattica = Gattica.new(:token => 'ga-token', :profile_id => 'ga-profile_id')
6
+ expected = "ids=ga:ga-profile_id&start-date=2008-01-02&end-date=2008-01-03&dimensions=ga:pageTitle,ga:pagePath&metrics=ga:pageviews&sort=-ga:pageviews&max-results=3"
7
+ result = @gattica.send(:build_query_string, {
8
+ :start_date => Date.civil(2008,1,2),
9
+ :end_date => Date.civil(2008,1,3),
10
+ :dimensions => ['pageTitle','pagePath'],
11
+ :metrics => ['pageviews'],
12
+ :sort => '-pageviews',
13
+ 'max-results' => '3'}, 'ga-profile_id')
14
+ assert_equal expected, result
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestUser < Test::Unit::TestCase
4
+ def setup
5
+
6
+ end
7
+
8
+ def test_can_create_user
9
+ assert Gattica::User.new('anonymous@anon.com','none')
10
+ end
11
+
12
+ def test_invalid_email
13
+ assert_raise GatticaError::InvalidEmail do Gattica::User.new('','') end
14
+ assert_raise ArgumentError do Gattica::User.new('') end
15
+ assert_raise GatticaError::InvalidEmail do Gattica::User.new('anonymous','none') end
16
+ assert_raise GatticaError::InvalidEmail do Gattica::User.new('anonymous@asdfcom','none') end
17
+ end
18
+
19
+ def test_invalid_password
20
+ assert_raise GatticaError::InvalidPassword do Gattica::User.new('anonymous@anon.com','') end
21
+ assert_raise ArgumentError do Gattica::User.new('anonymous@anon.com') end
22
+ end
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gattica
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Rob Cameron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.6.164
24
+ version:
25
+ description: Gattica is a Ruby library for extracting data from the Google Analytics API.
26
+ email: cannikinn@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .gitignore
36
+ - History.txt
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION.yml
41
+ - examples/example.rb
42
+ - gattica.gemspec
43
+ - lib/gattica.rb
44
+ - lib/gattica/account.rb
45
+ - lib/gattica/auth.rb
46
+ - lib/gattica/convertible.rb
47
+ - lib/gattica/core_extensions.rb
48
+ - lib/gattica/data_point.rb
49
+ - lib/gattica/data_set.rb
50
+ - lib/gattica/exceptions.rb
51
+ - lib/gattica/user.rb
52
+ - test/helper.rb
53
+ - test/suite.rb
54
+ - test/test_auth.rb
55
+ - test/test_engine.rb
56
+ - test/test_gattica.rb
57
+ - test/test_user.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/cannikin/gattica
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.3.5
83
+ signing_key:
84
+ specification_version: 2
85
+ summary: Gattica is a Ruby library for extracting data from the Google Analytics API.
86
+ test_files:
87
+ - test/helper.rb
88
+ - test/suite.rb
89
+ - test/test_auth.rb
90
+ - test/test_engine.rb
91
+ - test/test_gattica.rb
92
+ - test/test_user.rb
93
+ - examples/example.rb