chrisle-gattica 0.6.3

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,24 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/task'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "chrisle-gattica"
9
+ gemspec.summary = "Gattica is a easy to use Ruby Gem for getting data from the Google Analytics API."
10
+ gemspec.email = "chrisl@seerinteractive.com"
11
+ gemspec.homepage = "http://github.com/chrisle/gattica"
12
+ gemspec.description = "Gattica is a easy to use Ruby Gem for getting data from the Google Analytics API. It supports metrics, dimensions, sort, filters, goals, and segments. It can handle accounts with 1000+ profiles, and can return data in CSV, Hash, or JSON"
13
+ gemspec.authors = ["Christopher Le, et all"]
14
+ gemspec.add_dependency 'hpricot'
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
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,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 6
4
+ :patch: 3
5
+ :build: !!null
@@ -0,0 +1,68 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
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 = "chrisle-gattica"
8
+ s.version = "0.6.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christopher Le, et all"]
12
+ s.date = "2012-06-10"
13
+ s.description = "Gattica is a easy to use Ruby Gem for getting data from the Google Analytics API. It supports metrics, dimensions, sort, filters, goals, and segments. It can handle accounts with 1000+ profiles, and can return data in CSV, Hash, or JSON"
14
+ s.email = "chrisl@seerinteractive.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION.yml",
26
+ "chrisle-gattica.gemspec",
27
+ "lib/gattica.rb",
28
+ "lib/gattica/account.rb",
29
+ "lib/gattica/auth.rb",
30
+ "lib/gattica/convertible.rb",
31
+ "lib/gattica/data_point.rb",
32
+ "lib/gattica/data_set.rb",
33
+ "lib/gattica/engine.rb",
34
+ "lib/gattica/exceptions.rb",
35
+ "lib/gattica/goals.rb",
36
+ "lib/gattica/hash_extensions.rb",
37
+ "lib/gattica/profiles.rb",
38
+ "lib/gattica/segment.rb",
39
+ "lib/gattica/settings.rb",
40
+ "lib/gattica/user.rb",
41
+ "test/helper.rb",
42
+ "test/settings.rb",
43
+ "test/suite.rb",
44
+ "test/test_engine.rb",
45
+ "test/test_results.rb",
46
+ "test/test_user.rb"
47
+ ]
48
+ s.homepage = "http://github.com/chrisle/gattica"
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = "1.8.24"
51
+ s.summary = "Gattica is a easy to use Ruby Gem for getting data from the Google Analytics API."
52
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_runtime_dependency(%q<test-unit>, [">= 0"])
58
+ s.add_runtime_dependency(%q<hpricot>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<test-unit>, [">= 0"])
61
+ s.add_dependency(%q<hpricot>, [">= 0"])
62
+ end
63
+ else
64
+ s.add_dependency(%q<test-unit>, [">= 0"])
65
+ s.add_dependency(%q<hpricot>, [">= 0"])
66
+ end
67
+ end
68
+
@@ -0,0 +1,35 @@
1
+ $:.unshift File.dirname(__FILE__) # for use/testing when no gem is installed
2
+
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'uri'
6
+ require 'cgi'
7
+ require 'logger'
8
+ require 'rubygems'
9
+ require 'hpricot'
10
+ require 'yaml'
11
+
12
+ require 'gattica/engine'
13
+ require 'gattica/settings'
14
+ require 'gattica/hash_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
+ require 'gattica/segment'
23
+
24
+ # Gattica is a Ruby library for talking to the Google Analytics API.
25
+ # Please see the README for usage docs.
26
+ module Gattica
27
+
28
+ VERSION = '0.6.1'
29
+
30
+ # Creates a new instance of Gattica::Engine
31
+ def self.new(*args)
32
+ Engine.new(*args)
33
+ end
34
+
35
+ end
@@ -0,0 +1,54 @@
1
+ module Gattica
2
+ class Account
3
+ include Convertible
4
+
5
+ attr_reader :id, :updated, :title, :table_id, :account_id, :account_name,
6
+ :profile_id, :web_property_id, :goals
7
+
8
+ def initialize(xml)
9
+ @id = xml.at("link[@rel='self']").attributes['href']
10
+ @updated = DateTime.parse(xml.at(:updated).inner_html)
11
+ @account_id = find_account_id(xml)
12
+
13
+ @title = xpath_value(xml, "dxp:property[@name='ga:profileName']")
14
+ @table_id = xpath_value(xml, "dxp:property[@name='dxp:tableId']")
15
+ @profile_id = find_profile_id(xml)
16
+ @web_property_id = xpath_value(xml, "dxp:property[@name='ga:webPropertyId']")
17
+ @goals = []
18
+ end
19
+
20
+ def xpath_value(xml, xpath)
21
+ xml.at(xpath).attributes['value']
22
+ end
23
+
24
+ def find_account_id(xml)
25
+ xml.at("dxp:property[@name='ga:accountId']").attributes['value'].to_i
26
+ end
27
+
28
+ def find_account_name(xml)
29
+ xml.at("dxp:property[@name='ga:accountName']").attributes['value']
30
+ end
31
+
32
+ def find_profile_id(xml)
33
+ xml.at("dxp:property[@name='ga:profileId']").attributes['value'].to_i
34
+ end
35
+
36
+ def set_account_name(account_feed_entry)
37
+ if @account_id == find_account_id(account_feed_entry)
38
+ @account_name = find_account_name(account_feed_entry)
39
+ end
40
+ end
41
+
42
+ def set_goals(goals_feed_entry)
43
+ if @profile_id == find_profile_id(goals_feed_entry)
44
+ goal = goals_feed_entry.search('ga:goal').first
45
+ @goals.push({
46
+ :active => goal.attributes['active'],
47
+ :name => goal.attributes['name'],
48
+ :number => goal.attributes['number'].to_i,
49
+ :value => goal.attributes['value'].to_f
50
+ })
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,47 @@
1
+ module Gattica
2
+ class Auth
3
+ include Convertible
4
+
5
+ SCRIPT_NAME = '/accounts/ClientLogin'
6
+ 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
7
+ OPTIONS = { :source => 'gattica', :service => 'analytics' } # Google asks that you provide the name of your app as a 'source' parameter in your POST
8
+
9
+ attr_reader :tokens
10
+
11
+ # Try to authenticate the user
12
+ def initialize(http, user)
13
+ options = OPTIONS.merge(user.to_h)
14
+ options.extend HashExtensions
15
+
16
+ response = http.post(SCRIPT_NAME, options.to_query, HEADERS)
17
+ data = response.body ||= ''
18
+ if response.code != '200'
19
+ case response.code
20
+ when '403'
21
+ raise GatticaError::CouldNotAuthenticate, 'Your email and/or password is not recognized by the Google ClientLogin system (status code: 403)'
22
+ else
23
+ raise GatticaError::UnknownAnalyticsError, response.body + " (status code: #{response.code})"
24
+ end
25
+ end
26
+ @tokens = parse_tokens(data)
27
+ end
28
+
29
+
30
+ private
31
+
32
+ # Parse the authentication tokens out of the response and makes them available as a hash
33
+ #
34
+ # tokens[:auth] => Google requires this for every request (added to HTTP headers on GET requests)
35
+ # tokens[:sid] => Not used
36
+ # tokens[:lsid] => Not used
37
+
38
+ def parse_tokens(data)
39
+ tokens = {}
40
+ data.split("\n").each do |t|
41
+ tokens.merge!({ t.split('=').first.downcase.to_sym => t.split('=').last })
42
+ end
43
+ return tokens
44
+ end
45
+
46
+ end
47
+ 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.tap { |h| h.include? HashExtensions }
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,73 @@
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(':').last }
21
+ end
22
+ @metrics = xml.search('dxp:metric').collect do |metric|
23
+ { metric.attributes['name'].split(':').last.to_sym => metric.attributes['value'].split(':').last.to_f }
24
+ end
25
+ end
26
+
27
+
28
+ # Outputs in Comma Seperated Values format
29
+ def to_csv(format = :short)
30
+ output = ''
31
+ columns = []
32
+
33
+ # only output
34
+ case format
35
+ when :long
36
+
37
+ [@id, @updated, @title].each { |c| columns << c }
38
+ end
39
+
40
+ # output all dimensions
41
+
42
+ @dimensions.map {|d| d.values.first}.each { |c| columns << c }
43
+ # output all metrics
44
+ @metrics.map {|m| m.values.first}.each { |c| columns << c }
45
+
46
+ output = CSV.generate_line(columns)
47
+
48
+ end
49
+
50
+
51
+ def to_yaml
52
+ { 'id' => @id,
53
+ 'updated' => @updated,
54
+ 'title' => @title,
55
+ 'dimensions' => @dimensions,
56
+ 'metrics' => @metrics }.to_yaml
57
+ end
58
+
59
+ def to_hash
60
+
61
+ res_hash = {}
62
+
63
+ @dimensions.each{|d| res_hash.merge!(d) }
64
+ # output all metrics
65
+ @metrics.each{|m| res_hash.merge!(m) }
66
+ res_hash
67
+
68
+ end
69
+
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,57 @@
1
+ module Gattica
2
+
3
+ # Encapsulates the data returned by the GA API
4
+ class DataSet
5
+ include Convertible
6
+
7
+ attr_reader :total_results, :start_index, :items_per_page, :start_date,
8
+ :end_date, :points, :xml
9
+
10
+ def initialize(xml)
11
+ @xml = xml.to_s
12
+ @total_results = xml.at('openSearch:totalResults').inner_html.to_i
13
+ @start_index = xml.at('openSearch:startIndex').inner_html.to_i
14
+ @items_per_page = xml.at('openSearch:itemsPerPage').inner_html.to_i
15
+ @start_date = Date.parse(xml.at('dxp:startDate').inner_html)
16
+ @end_date = Date.parse(xml.at('dxp:endDate').inner_html)
17
+ @points = xml.search(:entry).collect { |entry| DataPoint.new(entry) }
18
+ end
19
+
20
+ # Returns a string formatted as a CSV containing just the data points.
21
+ #
22
+ # == Parameters:
23
+ # +format=:long+:: Adds id, updated, title to output columns
24
+ def to_csv(format=:short)
25
+ output = ''
26
+ columns = []
27
+ case format
28
+ when :long
29
+ ["id", "updated", "title"].each { |c| columns << c }
30
+ end
31
+ unless @points.empty? # if there was at least one result
32
+ @points.first.dimensions.map {|d| d.keys.first}.each { |c| columns << c }
33
+ @points.first.metrics.map {|m| m.keys.first}.each { |c| columns << c }
34
+ end
35
+ output = CSV.generate_line(columns)
36
+ @points.each do |point|
37
+ output += point.to_csv(format)
38
+ end
39
+ output
40
+ end
41
+
42
+ def to_yaml
43
+ { 'total_results' => @total_results,
44
+ 'start_index' => @start_index,
45
+ 'items_per_page' => @items_per_page,
46
+ 'start_date' => @start_date,
47
+ 'end_date' => @end_date,
48
+ 'points' => @points }.to_yaml
49
+ end
50
+
51
+ def to_hash
52
+ @points.map(&:to_hash)
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,295 @@
1
+ module Gattica
2
+ class Engine
3
+
4
+ attr_reader :user
5
+ attr_accessor :profile_id, :token, :user_accounts
6
+
7
+ # Initialize Gattica using username/password or token.
8
+ #
9
+ # == Options:
10
+ # To change the defaults see link:settings.rb
11
+ # +:debug+:: Send debug info to the logger (default is false)
12
+ # +:email+:: Your email/login for Google Analytics
13
+ # +:headers+:: Add additional HTTP headers (default is {} )
14
+ # +:logger+:: Logger to use (default is STDOUT)
15
+ # +:password+:: Your password for Google Analytics
16
+ # +:profile_id+:: Use this Google Analytics profile_id (default is nil)
17
+ # +:timeout+:: Set Net:HTTP timeout in seconds (default is 300)
18
+ # +:token+:: Use an authentication token you received before
19
+ def initialize(options={})
20
+ @options = Settings::DEFAULT_OPTIONS.merge(options)
21
+ handle_init_options(@options)
22
+ create_http_connection('www.google.com')
23
+ check_init_auth_requirements()
24
+ end
25
+
26
+ # Returns the list of accounts the user has access to. A user may have
27
+ # multiple accounts on Google Analytics and each account may have multiple
28
+ # profiles. You need the profile_id in order to get info from GA. If you
29
+ # don't know the profile_id then use this method to get a list of all them.
30
+ # Then set the profile_id of your instance and you can make regular calls
31
+ # from then on.
32
+ #
33
+ # ga = Gattica.new({:email => 'johndoe@google.com', :password => 'password'})
34
+ # ga.accounts
35
+ # # you parse through the accounts to find the profile_id you need
36
+ # ga.profile_id = 12345678
37
+ # # now you can perform a regular search, see Gattica::Engine#get
38
+ #
39
+ # If you pass in a profile id when you instantiate Gattica::Search then you won't need to
40
+ # get the accounts and find a profile_id - you apparently already know it!
41
+ #
42
+ # See Gattica::Engine#get to see how to get some data.
43
+
44
+ def accounts
45
+ if @user_accounts.nil?
46
+ create_http_connection('www.googleapis.com')
47
+
48
+ # get profiles
49
+ response = do_http_get("/analytics/v2.4/management/accounts/~all/webproperties/~all/profiles?max-results=10000")
50
+ xml = Hpricot(response)
51
+ @user_accounts = xml.search(:entry).collect { |profile_xml|
52
+ Account.new(profile_xml)
53
+ }
54
+
55
+ # Fill in the goals
56
+ response = do_http_get("/analytics/v2.4/management/accounts/~all/webproperties/~all/profiles/~all/goals?max-results=10000")
57
+ xml = Hpricot(response)
58
+ @user_accounts.each do |ua|
59
+ xml.search(:entry).each { |e| ua.set_goals(e) }
60
+ end
61
+
62
+ # Fill in the account name
63
+ response = do_http_get("/analytics/v2.4/management/accounts?max-results=10000")
64
+ xml = Hpricot(response)
65
+ @user_accounts.each do |ua|
66
+ xml.search(:entry).each { |e| ua.set_account_name(e) }
67
+ end
68
+
69
+ end
70
+ @user_accounts
71
+ end
72
+
73
+ # Returns the list of segments available to the authenticated user.
74
+ #
75
+ # == Usage
76
+ # ga = Gattica.new({:email => 'johndoe@google.com', :password => 'password'})
77
+ # ga.segments # Look up segment id
78
+ # my_gaid = 'gaid::-5' # Non-paid Search Traffic
79
+ # ga.profile_id = 12345678 # Set our profile ID
80
+ #
81
+ # gs.get({ :start_date => '2008-01-01',
82
+ # :end_date => '2008-02-01',
83
+ # :dimensions => 'month',
84
+ # :metrics => 'views',
85
+ # :segment => my_gaid })
86
+
87
+ def segments
88
+ if @user_segments.nil?
89
+ response = do_http_get("/analytics/v2.4/management/segments?max-results=10000")
90
+ xml = Hpricot(response)
91
+ @user_segments = xml.search("dxp:segment").collect { |s|
92
+ Segment.new(s)
93
+ }
94
+ end
95
+ return @user_segments
96
+ end
97
+
98
+ # This is the method that performs the actual request to get data.
99
+ #
100
+ # == Usage
101
+ #
102
+ # gs = Gattica.new({:email => 'johndoe@google.com', :password => 'password', :profile_id => 123456})
103
+ # gs.get({ :start_date => '2008-01-01',
104
+ # :end_date => '2008-02-01',
105
+ # :dimensions => 'browser',
106
+ # :metrics => 'pageviews',
107
+ # :sort => 'pageviews',
108
+ # :filters => ['browser == Firefox']})
109
+ #
110
+ # == Input
111
+ #
112
+ # When calling +get+ you'll pass in a hash of options. For a description of what these mean to
113
+ # Google Analytics, see http://code.google.com/apis/analytics/docs
114
+ #
115
+ # Required values are:
116
+ #
117
+ # * +start_date+ => Beginning of the date range to search within
118
+ # * +end_date+ => End of the date range to search within
119
+ #
120
+ # Optional values are:
121
+ #
122
+ # * +dimensions+ => an array of GA dimensions (without the ga: prefix)
123
+ # * +metrics+ => an array of GA metrics (without the ga: prefix)
124
+ # * +filter+ => an array of GA dimensions/metrics you want to filter by (without the ga: prefix)
125
+ # * +sort+ => an array of GA dimensions/metrics you want to sort by (without the ga: prefix)
126
+ #
127
+ # == Exceptions
128
+ #
129
+ # If a user doesn't have access to the +profile_id+ you specified, you'll receive an error.
130
+ # Likewise, if you attempt to access a dimension or metric that doesn't exist, you'll get an
131
+ # error back from Google Analytics telling you so.
132
+
133
+ def get(args={})
134
+ args = validate_and_clean(Settings::DEFAULT_ARGS.merge(args))
135
+ query_string = build_query_string(args,@profile_id)
136
+ @logger.debug(query_string) if @debug
137
+ create_http_connection('www.googleapis.com')
138
+ data = do_http_get("/analytics/v2.4/data?#{query_string}")
139
+ return DataSet.new(Hpricot.XML(data))
140
+ end
141
+
142
+
143
+ # Since google wants the token to appear in any HTTP call's header, we have to set that header
144
+ # again any time @token is changed so we override the default writer (note that you need to set
145
+ # @token with self.token= instead of @token=)
146
+
147
+ def token=(token)
148
+ @token = token
149
+ set_http_headers
150
+ end
151
+
152
+ ######################################################################
153
+ private
154
+
155
+ # Does the work of making HTTP calls and then going through a suite of tests on the response to make
156
+ # sure it's valid and not an error
157
+
158
+ def do_http_get(query_string)
159
+ response = @http.get(query_string, @headers)
160
+
161
+ # error checking
162
+ if response.code != '200'
163
+ case response.code
164
+ when '400'
165
+ raise GatticaError::AnalyticsError, response.body + " (status code: #{response.code})"
166
+ when '401'
167
+ raise GatticaError::InvalidToken, "Your authorization token is invalid or has expired (status code: #{response.code})"
168
+ else # some other unknown error
169
+ raise GatticaError::UnknownAnalyticsError, response.body + " (status code: #{response.code})"
170
+ end
171
+ end
172
+
173
+ return response.body
174
+ end
175
+
176
+
177
+ # Sets up the HTTP headers that Google expects (this is called any time @token is set either by Gattica
178
+ # or manually by the user since the header must include the token)
179
+ def set_http_headers
180
+ @headers['Authorization'] = "GoogleLogin auth=#{@token}"
181
+ @headers['GData-Version']= '2'
182
+ end
183
+
184
+
185
+ # Creates a valid query string for GA
186
+ def build_query_string(args,profile)
187
+ output = "ids=ga:#{profile}&start-date=#{args[:start_date]}&end-date=#{args[:end_date]}"
188
+ if (start_index = args[:start_index].to_i) > 0
189
+ output += "&start-index=#{start_index}"
190
+ end
191
+ unless args[:dimensions].empty?
192
+ output += '&dimensions=' + args[:dimensions].collect do |dimension|
193
+ "ga:#{dimension}"
194
+ end.join(',')
195
+ end
196
+ unless args[:metrics].empty?
197
+ output += '&metrics=' + args[:metrics].collect do |metric|
198
+ "ga:#{metric}"
199
+ end.join(',')
200
+ end
201
+ unless args[:sort].empty?
202
+ output += '&sort=' + args[:sort].collect do |sort|
203
+ sort[0..0] == '-' ? "-ga:#{sort[1..-1]}" : "ga:#{sort}" # if the first character is a dash, move it before the ga:
204
+ end.join(',')
205
+ end
206
+ unless args[:segment].nil?
207
+ output += "&segment=#{args[:segment]}"
208
+ end
209
+ unless args[:max_results].nil?
210
+ output += "&max-results=#{args[:max_results]}"
211
+ end
212
+
213
+ # 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)
214
+ unless args[:filters].empty? # filters are a little more complicated because they can have all kinds of modifiers
215
+ output += '&filters=' + args[:filters].collect do |filter|
216
+ match, name, operator, expression = *filter.match(/^(\w*)\s*([=!<>~@]*)\s*(.*)$/) # splat the resulting Match object to pull out the parts automatically
217
+ unless name.empty? || operator.empty? || expression.empty? # make sure they all contain something
218
+ "ga:#{name}#{CGI::escape(operator.gsub(/ /,''))}#{CGI::escape(expression)}" # remove any whitespace from the operator before output
219
+ else
220
+ raise GatticaError::InvalidFilter, "The filter '#{filter}' is invalid. Filters should look like 'browser == Firefox' or 'browser==Firefox'"
221
+ end
222
+ end.join(';')
223
+ end
224
+ return output
225
+ end
226
+
227
+
228
+ # Validates that the args passed to +get+ are valid
229
+ def validate_and_clean(args)
230
+
231
+ raise GatticaError::MissingStartDate, ':start_date is required' if args[:start_date].nil? || args[:start_date].empty?
232
+ raise GatticaError::MissingEndDate, ':end_date is required' if args[:end_date].nil? || args[:end_date].empty?
233
+ raise GatticaError::TooManyDimensions, 'You can only have a maximum of 7 dimensions' if args[:dimensions] && (args[:dimensions].is_a?(Array) && args[:dimensions].length > 7)
234
+ raise GatticaError::TooManyMetrics, 'You can only have a maximum of 10 metrics' if args[:metrics] && (args[:metrics].is_a?(Array) && args[:metrics].length > 10)
235
+
236
+ possible = args[:dimensions] + args[:metrics]
237
+
238
+ # make sure that the user is only trying to sort fields that they've previously included with dimensions and metrics
239
+ if args[:sort]
240
+ missing = args[:sort].find_all do |arg|
241
+ !possible.include? arg.gsub(/^-/,'') # remove possible minuses from any sort params
242
+ end
243
+ unless missing.empty?
244
+ raise GatticaError::InvalidSort, "You are trying to sort by fields that are not in the available dimensions or metrics: #{missing.join(', ')}"
245
+ end
246
+ end
247
+
248
+ # make sure that the user is only trying to filter fields that are in dimensions or metrics
249
+ if args[:filters]
250
+ missing = args[:filters].find_all do |arg|
251
+ !possible.include? arg.match(/^\w*/).to_s # get the name of the filter and compare
252
+ end
253
+ unless missing.empty?
254
+ raise GatticaError::InvalidSort, "You are trying to filter by fields that are not in the available dimensions or metrics: #{missing.join(', ')}"
255
+ end
256
+ end
257
+
258
+ return args
259
+ end
260
+
261
+ def create_http_connection(server)
262
+ port = Settings::USE_SSL ? Settings::SSL_PORT : Settings::NON_SSL_PORT
263
+ @http = Net::HTTP.new(server, port)
264
+ @http.use_ssl = Settings::USE_SSL
265
+ @http.set_debug_output $stdout if @options[:debug]
266
+ @http.read_timeout = @options[:timeout] if @options[:timeout]
267
+ end
268
+
269
+ # Sets instance variables from options given during initialization and
270
+ def handle_init_options(options)
271
+ @logger = options[:logger]
272
+ @profile_id = options[:profile_id]
273
+ @user_accounts = nil # filled in later if the user ever calls Gattica::Engine#accounts
274
+ @user_segments = nil
275
+ @headers = { }.merge(options[:headers]) # headers used for any HTTP requests (Google requires a special 'Authorization' header which is set any time @token is set)
276
+ @default_account_feed = nil
277
+
278
+ end
279
+
280
+ # If the authorization is a email and password then create User objects
281
+ # or if it's a previous token, use that. Else, raise exception.
282
+ def check_init_auth_requirements
283
+ if @options[:token].to_s.length > 200
284
+ self.token = @options[:token]
285
+ elsif @options[:email] && @options[:password]
286
+ @user = User.new(@options[:email], @options[:password])
287
+ @auth = Auth.new(@http, user)
288
+ self.token = @auth.tokens[:auth]
289
+ else
290
+ raise GatticaError::NoLoginOrToken, 'An email and password or an authentication token is required to initialize Gattica.'
291
+ end
292
+ end
293
+
294
+ end
295
+ end