readmedia-gattica 0.6.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
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 = "readmedia-gattica"
9
+ gemspec.version = "0.6.2.1"
10
+ gemspec.summary = "Gattica is a easy to use Ruby Gem for getting data from the Google Analytics API."
11
+ gemspec.email = "Philip@readMedia.com"
12
+ gemspec.homepage = "http://github.com/readmedia/gattica"
13
+ gemspec.description = "(rM: Fixing problem with to_csv.) 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
+ gemspec.authors = ["Christopher Le, et all, Philip Brocoum (readMedia)"]
15
+ gemspec.add_dependency 'hpricot'
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ Rake::TestTask.new do |t|
22
+ t.libs << 'lib'
23
+ t.pattern = 'test/**/test_*.rb'
24
+ t.verbose = false
25
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 6
4
+ :patch: 1
5
+ :build: !!null
data/gattica.gemspec ADDED
@@ -0,0 +1,81 @@
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 = "gattica"
8
+ s.version = "0.6.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christopher Le, et all, Philip Brocoum (readMedia)"]
12
+ s.date = "2012-09-05"
13
+ s.description = "(rM: Fixing problem with to_csv.) 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 = "Philip@readMedia.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "Gemfile.lock.orig",
23
+ "LICENSE",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION.yml",
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/data_point.rb",
33
+ "lib/gattica/data_set.rb",
34
+ "lib/gattica/engine.rb",
35
+ "lib/gattica/exceptions.rb",
36
+ "lib/gattica/goals.rb",
37
+ "lib/gattica/hash_extensions.rb",
38
+ "lib/gattica/profiles.rb",
39
+ "lib/gattica/segment.rb",
40
+ "lib/gattica/settings.rb",
41
+ "lib/gattica/user.rb",
42
+ "test/helper.rb",
43
+ "test/settings.rb",
44
+ "test/suite.rb",
45
+ "test/test_engine.rb",
46
+ "test/test_results.rb",
47
+ "test/test_user.rb"
48
+ ]
49
+ s.homepage = "http://github.com/readmedia/gattica"
50
+ s.require_paths = ["lib"]
51
+ s.rubygems_version = "1.3.9.4"
52
+ s.summary = "Gattica is a easy to use Ruby Gem for getting data from the Google Analytics API."
53
+
54
+ if s.respond_to? :specification_version then
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_runtime_dependency(%q<gattica>, [">= 0"])
59
+ s.add_runtime_dependency(%q<test-unit>, [">= 0"])
60
+ s.add_runtime_dependency(%q<hpricot>, [">= 0"])
61
+ s.add_runtime_dependency(%q<test-unit>, [">= 0"])
62
+ s.add_runtime_dependency(%q<bundler>, [">= 0"])
63
+ s.add_runtime_dependency(%q<hpricot>, [">= 0"])
64
+ else
65
+ s.add_dependency(%q<gattica>, [">= 0"])
66
+ s.add_dependency(%q<test-unit>, [">= 0"])
67
+ s.add_dependency(%q<hpricot>, [">= 0"])
68
+ s.add_dependency(%q<test-unit>, [">= 0"])
69
+ s.add_dependency(%q<bundler>, [">= 0"])
70
+ s.add_dependency(%q<hpricot>, [">= 0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<gattica>, [">= 0"])
74
+ s.add_dependency(%q<test-unit>, [">= 0"])
75
+ s.add_dependency(%q<hpricot>, [">= 0"])
76
+ s.add_dependency(%q<test-unit>, [">= 0"])
77
+ s.add_dependency(%q<bundler>, [">= 0"])
78
+ s.add_dependency(%q<hpricot>, [">= 0"])
79
+ end
80
+ end
81
+
data/lib/gattica.rb ADDED
@@ -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.2.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.join("\n")
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,298 @@
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
+ # +:verify_ssl+:: Verify SSL connection (default is true)
20
+ def initialize(options={})
21
+ @options = Settings::DEFAULT_OPTIONS.merge(options)
22
+ handle_init_options(@options)
23
+ create_http_connection('www.google.com')
24
+ check_init_auth_requirements()
25
+ end
26
+
27
+ # Returns the list of accounts the user has access to. A user may have
28
+ # multiple accounts on Google Analytics and each account may have multiple
29
+ # profiles. You need the profile_id in order to get info from GA. If you
30
+ # don't know the profile_id then use this method to get a list of all them.
31
+ # Then set the profile_id of your instance and you can make regular calls
32
+ # from then on.
33
+ #
34
+ # ga = Gattica.new({:email => 'johndoe@google.com', :password => 'password'})
35
+ # ga.accounts
36
+ # # you parse through the accounts to find the profile_id you need
37
+ # ga.profile_id = 12345678
38
+ # # now you can perform a regular search, see Gattica::Engine#get
39
+ #
40
+ # If you pass in a profile id when you instantiate Gattica::Search then you won't need to
41
+ # get the accounts and find a profile_id - you apparently already know it!
42
+ #
43
+ # See Gattica::Engine#get to see how to get some data.
44
+
45
+ def accounts
46
+ if @user_accounts.nil?
47
+ create_http_connection('www.googleapis.com')
48
+
49
+ # get profiles
50
+ response = do_http_get("/analytics/v2.4/management/accounts/~all/webproperties/~all/profiles?max-results=10000")
51
+ xml = Hpricot(response)
52
+ @user_accounts = xml.search(:entry).collect { |profile_xml|
53
+ Account.new(profile_xml)
54
+ }
55
+
56
+ # Fill in the goals
57
+ response = do_http_get("/analytics/v2.4/management/accounts/~all/webproperties/~all/profiles/~all/goals?max-results=10000")
58
+ xml = Hpricot(response)
59
+ @user_accounts.each do |ua|
60
+ xml.search(:entry).each { |e| ua.set_goals(e) }
61
+ end
62
+
63
+ # Fill in the account name
64
+ response = do_http_get("/analytics/v2.4/management/accounts?max-results=10000")
65
+ xml = Hpricot(response)
66
+ @user_accounts.each do |ua|
67
+ xml.search(:entry).each { |e| ua.set_account_name(e) }
68
+ end
69
+
70
+ end
71
+ @user_accounts
72
+ end
73
+
74
+ # Returns the list of segments available to the authenticated user.
75
+ #
76
+ # == Usage
77
+ # ga = Gattica.new({:email => 'johndoe@google.com', :password => 'password'})
78
+ # ga.segments # Look up segment id
79
+ # my_gaid = 'gaid::-5' # Non-paid Search Traffic
80
+ # ga.profile_id = 12345678 # Set our profile ID
81
+ #
82
+ # gs.get({ :start_date => '2008-01-01',
83
+ # :end_date => '2008-02-01',
84
+ # :dimensions => 'month',
85
+ # :metrics => 'views',
86
+ # :segment => my_gaid })
87
+
88
+ def segments
89
+ if @user_segments.nil?
90
+ create_http_connection('www.googleapis.com')
91
+ response = do_http_get("/analytics/v2.4/management/segments?max-results=10000")
92
+ xml = Hpricot(response)
93
+ @user_segments = xml.search("dxp:segment").collect { |s|
94
+ Segment.new(s)
95
+ }
96
+ end
97
+ return @user_segments
98
+ end
99
+
100
+ # This is the method that performs the actual request to get data.
101
+ #
102
+ # == Usage
103
+ #
104
+ # gs = Gattica.new({:email => 'johndoe@google.com', :password => 'password', :profile_id => 123456})
105
+ # gs.get({ :start_date => '2008-01-01',
106
+ # :end_date => '2008-02-01',
107
+ # :dimensions => 'browser',
108
+ # :metrics => 'pageviews',
109
+ # :sort => 'pageviews',
110
+ # :filters => ['browser == Firefox']})
111
+ #
112
+ # == Input
113
+ #
114
+ # When calling +get+ you'll pass in a hash of options. For a description of what these mean to
115
+ # Google Analytics, see http://code.google.com/apis/analytics/docs
116
+ #
117
+ # Required values are:
118
+ #
119
+ # * +start_date+ => Beginning of the date range to search within
120
+ # * +end_date+ => End of the date range to search within
121
+ #
122
+ # Optional values are:
123
+ #
124
+ # * +dimensions+ => an array of GA dimensions (without the ga: prefix)
125
+ # * +metrics+ => an array of GA metrics (without the ga: prefix)
126
+ # * +filter+ => an array of GA dimensions/metrics you want to filter by (without the ga: prefix)
127
+ # * +sort+ => an array of GA dimensions/metrics you want to sort by (without the ga: prefix)
128
+ #
129
+ # == Exceptions
130
+ #
131
+ # If a user doesn't have access to the +profile_id+ you specified, you'll receive an error.
132
+ # Likewise, if you attempt to access a dimension or metric that doesn't exist, you'll get an
133
+ # error back from Google Analytics telling you so.
134
+
135
+ def get(args={})
136
+ args = validate_and_clean(Settings::DEFAULT_ARGS.merge(args))
137
+ query_string = build_query_string(args,@profile_id)
138
+ @logger.debug(query_string) if @debug
139
+ create_http_connection('www.googleapis.com')
140
+ data = do_http_get("/analytics/v2.4/data?#{query_string}")
141
+ return DataSet.new(Hpricot.XML(data))
142
+ end
143
+
144
+
145
+ # Since google wants the token to appear in any HTTP call's header, we have to set that header
146
+ # again any time @token is changed so we override the default writer (note that you need to set
147
+ # @token with self.token= instead of @token=)
148
+
149
+ def token=(token)
150
+ @token = token
151
+ set_http_headers
152
+ end
153
+
154
+ ######################################################################
155
+ private
156
+
157
+ # Does the work of making HTTP calls and then going through a suite of tests on the response to make
158
+ # sure it's valid and not an error
159
+
160
+ def do_http_get(query_string)
161
+ response = @http.get(query_string, @headers)
162
+
163
+ # error checking
164
+ if response.code != '200'
165
+ case response.code
166
+ when '400'
167
+ raise GatticaError::AnalyticsError, response.body + " (status code: #{response.code})\n#{query_string}"
168
+ when '401'
169
+ raise GatticaError::InvalidToken, "Your authorization token is invalid or has expired (status code: #{response.code})"
170
+ else # some other unknown error
171
+ raise GatticaError::UnknownAnalyticsError, response.body + " (status code: #{response.code})"
172
+ end
173
+ end
174
+
175
+ return response.body
176
+ end
177
+
178
+
179
+ # Sets up the HTTP headers that Google expects (this is called any time @token is set either by Gattica
180
+ # or manually by the user since the header must include the token)
181
+ def set_http_headers
182
+ @headers['Authorization'] = "GoogleLogin auth=#{@token}"
183
+ @headers['GData-Version']= '2'
184
+ end
185
+
186
+
187
+ # Creates a valid query string for GA
188
+ def build_query_string(args,profile)
189
+ output = "ids=ga:#{profile}&start-date=#{args[:start_date]}&end-date=#{args[:end_date]}"
190
+ if (start_index = args[:start_index].to_i) > 0
191
+ output += "&start-index=#{start_index}"
192
+ end
193
+ unless args[:dimensions].empty?
194
+ output += '&dimensions=' + args[:dimensions].collect do |dimension|
195
+ "ga:#{dimension}"
196
+ end.join(',')
197
+ end
198
+ unless args[:metrics].empty?
199
+ output += '&metrics=' + args[:metrics].collect do |metric|
200
+ "ga:#{metric}"
201
+ end.join(',')
202
+ end
203
+ unless args[:sort].empty?
204
+ output += '&sort=' + args[:sort].collect do |sort|
205
+ sort[0..0] == '-' ? "-ga:#{sort[1..-1]}" : "ga:#{sort}" # if the first character is a dash, move it before the ga:
206
+ end.join(',')
207
+ end
208
+ unless args[:segment].nil?
209
+ output += "&segment=#{args[:segment]}"
210
+ end
211
+ unless args[:max_results].nil?
212
+ output += "&max-results=#{args[:max_results]}"
213
+ end
214
+
215
+ # 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)
216
+ unless args[:filters].empty? # filters are a little more complicated because they can have all kinds of modifiers
217
+ output += '&filters=' + args[:filters].collect do |filter|
218
+ match, name, operator, expression = *filter.match(/^(\w*)\s*([=!<>~@]*)\s*(.*)$/) # splat the resulting Match object to pull out the parts automatically
219
+ unless name.empty? || operator.empty? || expression.empty? # make sure they all contain something
220
+ "ga:#{name}#{CGI::escape(operator.gsub(/ /,''))}#{CGI::escape(expression)}" # remove any whitespace from the operator before output
221
+ else
222
+ raise GatticaError::InvalidFilter, "The filter '#{filter}' is invalid. Filters should look like 'browser == Firefox' or 'browser==Firefox'"
223
+ end
224
+ end.join(';')
225
+ end
226
+ return output
227
+ end
228
+
229
+
230
+ # Validates that the args passed to +get+ are valid
231
+ def validate_and_clean(args)
232
+
233
+ raise GatticaError::MissingStartDate, ':start_date is required' if args[:start_date].nil? || args[:start_date].empty?
234
+ raise GatticaError::MissingEndDate, ':end_date is required' if args[:end_date].nil? || args[:end_date].empty?
235
+ raise GatticaError::TooManyDimensions, 'You can only have a maximum of 7 dimensions' if args[:dimensions] && (args[:dimensions].is_a?(Array) && args[:dimensions].length > 7)
236
+ raise GatticaError::TooManyMetrics, 'You can only have a maximum of 10 metrics' if args[:metrics] && (args[:metrics].is_a?(Array) && args[:metrics].length > 10)
237
+
238
+ possible = args[:dimensions] + args[:metrics]
239
+
240
+ # make sure that the user is only trying to sort fields that they've previously included with dimensions and metrics
241
+ if args[:sort]
242
+ missing = args[:sort].find_all do |arg|
243
+ !possible.include? arg.gsub(/^-/,'') # remove possible minuses from any sort params
244
+ end
245
+ unless missing.empty?
246
+ raise GatticaError::InvalidSort, "You are trying to sort by fields that are not in the available dimensions or metrics: #{missing.join(', ')}"
247
+ end
248
+ end
249
+
250
+ # make sure that the user is only trying to filter fields that are in dimensions or metrics
251
+ # if args[:filters]
252
+ # missing = args[:filters].find_all do |arg|
253
+ # !possible.include? arg.match(/^\w*/).to_s # get the name of the filter and compare
254
+ # end
255
+ # unless missing.empty?
256
+ # raise GatticaError::InvalidSort, "You are trying to filter by fields that are not in the available dimensions or metrics: #{missing.join(', ')}"
257
+ # end
258
+ # end
259
+
260
+ return args
261
+ end
262
+
263
+ def create_http_connection(server)
264
+ port = Settings::USE_SSL ? Settings::SSL_PORT : Settings::NON_SSL_PORT
265
+ @http = Net::HTTP.new(server, port)
266
+ @http.use_ssl = Settings::USE_SSL
267
+ @http.verify_mode = @options[:verify_ssl] ? Settings::VERIFY_SSL_MODE : Settings::NO_VERIFY_SSL_MODE
268
+ @http.set_debug_output $stdout if @options[:debug]
269
+ @http.read_timeout = @options[:timeout] if @options[:timeout]
270
+ end
271
+
272
+ # Sets instance variables from options given during initialization and
273
+ def handle_init_options(options)
274
+ @logger = options[:logger]
275
+ @profile_id = options[:profile_id]
276
+ @user_accounts = nil # filled in later if the user ever calls Gattica::Engine#accounts
277
+ @user_segments = nil
278
+ @headers = { }.merge(options[:headers]) # headers used for any HTTP requests (Google requires a special 'Authorization' header which is set any time @token is set)
279
+ @default_account_feed = nil
280
+
281
+ end
282
+
283
+ # If the authorization is a email and password then create User objects
284
+ # or if it's a previous token, use that. Else, raise exception.
285
+ def check_init_auth_requirements
286
+ if @options[:token].to_s.length > 200
287
+ self.token = @options[:token]
288
+ elsif @options[:email] && @options[:password]
289
+ @user = User.new(@options[:email], @options[:password])
290
+ @auth = Auth.new(@http, user)
291
+ self.token = @auth.tokens[:auth]
292
+ else
293
+ raise GatticaError::NoLoginOrToken, 'An email and password or an authentication token is required to initialize Gattica.'
294
+ end
295
+ end
296
+
297
+ end
298
+ end