gattica 0.4.0

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