legato 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/README.md +1 -1
- data/Rakefile +6 -0
- data/legato.gemspec +5 -1
- data/lib/legato.rb +1 -1
- data/lib/legato/collection.rb +5 -0
- data/lib/legato/filter.rb +13 -7
- data/lib/legato/management/finder.rb +1 -1
- data/lib/legato/management/profile.rb +2 -1
- data/lib/legato/model.rb +2 -2
- data/lib/legato/query.rb +32 -15
- data/lib/legato/request.rb +1 -1
- data/lib/legato/response.rb +16 -61
- data/lib/legato/version.rb +1 -1
- data/spec/lib/legato/filter_spec.rb +13 -3
- data/spec/lib/legato/management/profile_spec.rb +2 -1
- data/spec/lib/legato/model_spec.rb +4 -4
- data/spec/lib/legato/query_spec.rb +60 -31
- data/spec/lib/legato/response_spec.rb +14 -1
- data/spec/support/examples/management_finder.rb +2 -2
- metadata +144 -78
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -143,7 +143,7 @@ Operators on dimensions:
|
|
143
143
|
* :end_date - The date to end, inclusive
|
144
144
|
* :limit - The maximum number of results to be returned
|
145
145
|
* :offset - The starting index
|
146
|
-
* :
|
146
|
+
* :sort - metric/dimension to sort by
|
147
147
|
|
148
148
|
## License ##
|
149
149
|
|
data/Rakefile
CHANGED
@@ -2,6 +2,12 @@ require "bundler/gem_tasks"
|
|
2
2
|
|
3
3
|
require 'oauth2'
|
4
4
|
|
5
|
+
# get oauth2 via device code, unimplemented as of now
|
6
|
+
# curl -d "client_id={{client_id}}&scope=https://www.googleapis.com/auth/analytics.readonly" https://accounts.google.com/o/oauth2/device/code
|
7
|
+
# { "device_code" : "4/8O1xUKWzOdJESG7ednnulZPsbyNt", "user_code" : "3tywhirg", "verification_url" : "http://www.google.com/device", "expires_in" : 1800, "interval" : 5 }
|
8
|
+
# curl -d "client_id={{client_id}}&client_secret={{client_secret}}&code=4/8O1xUKWzOdJESG7ednnulZPsbyNt&grant_type=http://oauth.net/grant_type/device/1.0" https://accounts.google.com/o/oauth2/token
|
9
|
+
# { "access_token" : "ERspXATXoblahblahblah", "token_type" : "Bearer", "expires_in" : 3600, "refresh_token" : "1/balhaajsdfklasfdjs;df" }
|
10
|
+
|
5
11
|
namespace :oauth do
|
6
12
|
def client
|
7
13
|
# This is my test client account for Legato.
|
data/legato.gemspec
CHANGED
@@ -3,6 +3,8 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
require "legato/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
+
s.required_ruby_version = '>= 1.9.2'
|
7
|
+
|
6
8
|
s.name = "legato"
|
7
9
|
s.version = Legato::VERSION
|
8
10
|
s.authors = ["Tony Pitale"]
|
@@ -11,7 +13,7 @@ Gem::Specification.new do |s|
|
|
11
13
|
s.summary = %q{Access the Google Analytics API with Ruby}
|
12
14
|
s.description = %q{Access the Google Analytics Core Reporting and Management APIs with Ruby. Create models for metrics and dimensions. Filter your data to tell you what you need.}
|
13
15
|
|
14
|
-
s.rubyforge_project = "legato"
|
16
|
+
s.rubyforge_project = "legato"
|
15
17
|
|
16
18
|
s.files = `git ls-files`.split("\n")
|
17
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -19,11 +21,13 @@ Gem::Specification.new do |s|
|
|
19
21
|
s.require_paths = ["lib"]
|
20
22
|
|
21
23
|
# specify any dependencies here; for example:
|
24
|
+
s.add_development_dependency "rake"
|
22
25
|
s.add_development_dependency "rspec"
|
23
26
|
s.add_development_dependency "mocha"
|
24
27
|
s.add_development_dependency "bourne"
|
25
28
|
s.add_development_dependency "vcr", "2.0.0.beta2"
|
26
29
|
s.add_development_dependency "fakeweb"
|
27
30
|
|
31
|
+
s.add_runtime_dependency "multi_json"
|
28
32
|
s.add_runtime_dependency "oauth2"
|
29
33
|
end
|
data/lib/legato.rb
CHANGED
data/lib/legato/filter.rb
CHANGED
@@ -3,27 +3,29 @@ module Legato
|
|
3
3
|
attr_accessor :field, :operator, :value, :join_character
|
4
4
|
|
5
5
|
OPERATORS = {
|
6
|
+
# metrics
|
6
7
|
:eql => '==',
|
7
8
|
:not_eql => '!=',
|
8
9
|
:gt => '>',
|
9
10
|
:gte => '>=',
|
10
11
|
:lt => '<',
|
11
12
|
:lte => '<=',
|
13
|
+
# dimensions
|
12
14
|
:matches => '==',
|
13
15
|
:does_not_match => '!=',
|
14
|
-
:contains => '=~',
|
15
|
-
:does_not_contain => '!~',
|
16
16
|
:substring => '=@',
|
17
17
|
:not_substring => '!@',
|
18
|
-
:
|
19
|
-
:
|
18
|
+
:contains => '=~', # regex
|
19
|
+
:does_not_contain => '!~' # regex
|
20
|
+
# :desc => '-',
|
21
|
+
# :descending => '-'
|
20
22
|
}
|
21
23
|
|
22
|
-
def initialize(field, operator, value, join_character
|
24
|
+
def initialize(field, operator, value, join_character)
|
23
25
|
self.field = field
|
24
26
|
self.operator = operator
|
25
27
|
self.value = value
|
26
|
-
self.join_character = join_character
|
28
|
+
self.join_character = join_character # if nil, will be overridden by Query#apply_filter
|
27
29
|
end
|
28
30
|
|
29
31
|
def google_field
|
@@ -34,8 +36,12 @@ module Legato
|
|
34
36
|
OPERATORS[operator]
|
35
37
|
end
|
36
38
|
|
39
|
+
# escape comma and semicolon in value to differentiate
|
40
|
+
# from those used as join characters for OR/AND
|
37
41
|
def escaped_value
|
38
|
-
|
42
|
+
# backslash is escaped in strings
|
43
|
+
# oauth will cgi/uri escape for us
|
44
|
+
value.to_s.gsub(/([,;])/) {|c| '\\'+c}
|
39
45
|
end
|
40
46
|
|
41
47
|
def to_param
|
@@ -13,12 +13,13 @@ module Legato
|
|
13
13
|
self.class.default_path + "/" + id.to_s
|
14
14
|
end
|
15
15
|
|
16
|
-
attr_accessor :id, :name, :user
|
16
|
+
attr_accessor :id, :name, :web_property_id, :user
|
17
17
|
|
18
18
|
def initialize(attributes, user)
|
19
19
|
self.user = user
|
20
20
|
self.id = attributes['id']
|
21
21
|
self.name = attributes['name']
|
22
|
+
self.web_property_id = attributes['webPropertyId']
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.for_account(account)
|
data/lib/legato/model.rb
CHANGED
@@ -18,11 +18,11 @@ module Legato
|
|
18
18
|
@filters ||= {}
|
19
19
|
end
|
20
20
|
|
21
|
-
def filter(name, block)
|
21
|
+
def filter(name, &block)
|
22
22
|
filters[name] = block
|
23
23
|
|
24
24
|
(class << self; self; end).instance_eval do
|
25
|
-
define_method(name) {|*args| Query.new(self).apply_filter(*args, block)}
|
25
|
+
define_method(name) {|*args| Query.new(self).apply_filter(*args, &block)}
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
data/lib/legato/query.rb
CHANGED
@@ -3,18 +3,19 @@ module Legato
|
|
3
3
|
include Enumerable
|
4
4
|
|
5
5
|
MONTH = 2592000
|
6
|
+
REQUEST_FIELDS = 'columnHeaders/name,rows,totalResults,totalsForAllResults'
|
6
7
|
|
7
|
-
def define_filter(name, block)
|
8
|
+
def define_filter(name, &block)
|
8
9
|
(class << self; self; end).instance_eval do
|
9
|
-
define_method(name) {|*args| apply_filter(*args, block)}
|
10
|
+
define_method(name) {|*args| apply_filter(*args, &block)}
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
def self.define_filter_operators(*methods)
|
14
15
|
methods.each do |method|
|
15
16
|
class_eval <<-CODE
|
16
|
-
def #{method}(field, value)
|
17
|
-
Filter.new(field, :#{method}, value)
|
17
|
+
def #{method}(field, value, join_character=nil)
|
18
|
+
Filter.new(field, :#{method}, value, join_character)
|
18
19
|
end
|
19
20
|
CODE
|
20
21
|
end
|
@@ -22,7 +23,7 @@ module Legato
|
|
22
23
|
|
23
24
|
attr_reader :parent_klass
|
24
25
|
attr_accessor :profile, :start_date, :end_date
|
25
|
-
attr_accessor :
|
26
|
+
attr_accessor :sort, :limit, :offset#, :segment # individual, overwritten
|
26
27
|
attr_accessor :filters # appended to, may add :segments later for dynamic segments
|
27
28
|
|
28
29
|
def initialize(klass)
|
@@ -33,7 +34,7 @@ module Legato
|
|
33
34
|
self.end_date = Time.now
|
34
35
|
|
35
36
|
klass.filters.each do |name, block|
|
36
|
-
define_filter(name, block)
|
37
|
+
define_filter(name, &block)
|
37
38
|
end
|
38
39
|
|
39
40
|
# may add later for dynamic segments
|
@@ -42,14 +43,14 @@ module Legato
|
|
42
43
|
# end
|
43
44
|
end
|
44
45
|
|
45
|
-
def apply_filter(*args, block)
|
46
|
+
def apply_filter(*args, &block)
|
46
47
|
@profile = extract_profile(args)
|
47
48
|
|
48
49
|
join_character = Legato.and_join_character # filters are joined by AND
|
49
50
|
|
50
51
|
# # block returns one filter or an array of filters
|
51
52
|
Array.wrap(instance_exec(*args, &block)).each do |filter|
|
52
|
-
filter.join_character
|
53
|
+
filter.join_character ||= join_character # only set when not set explicitly
|
53
54
|
self.filters << filter
|
54
55
|
|
55
56
|
join_character = Legato.or_join_character # arrays are joined by OR
|
@@ -60,7 +61,7 @@ module Legato
|
|
60
61
|
def apply_options(options)
|
61
62
|
if options.has_key?(:sort)
|
62
63
|
# warn
|
63
|
-
options[:
|
64
|
+
options[:sort] = options.delete(:sort)
|
64
65
|
end
|
65
66
|
|
66
67
|
apply_basic_options(options)
|
@@ -70,7 +71,7 @@ module Legato
|
|
70
71
|
end
|
71
72
|
|
72
73
|
def apply_basic_options(options)
|
73
|
-
[:
|
74
|
+
[:sort, :limit, :offset, :start_date, :end_date].each do |key| #:segment
|
74
75
|
self.send("#{key}=".to_sym, options[key]) if options.has_key?(key)
|
75
76
|
end
|
76
77
|
end
|
@@ -108,7 +109,10 @@ module Legato
|
|
108
109
|
end
|
109
110
|
|
110
111
|
def load
|
111
|
-
|
112
|
+
response = request_for_query
|
113
|
+
@collection = response.collection
|
114
|
+
@total_results = response.total_results
|
115
|
+
@totals_for_all_results = response.totals_for_all_results
|
112
116
|
@loaded = true
|
113
117
|
end
|
114
118
|
|
@@ -118,12 +122,24 @@ module Legato
|
|
118
122
|
end
|
119
123
|
alias :to_a :collection
|
120
124
|
|
125
|
+
def total_results
|
126
|
+
load unless loaded?
|
127
|
+
@total_results
|
128
|
+
end
|
129
|
+
|
130
|
+
def totals_for_all_results
|
131
|
+
load unless loaded?
|
132
|
+
@totals_for_all_results
|
133
|
+
end
|
134
|
+
|
121
135
|
def each(&block)
|
122
136
|
collection.each(&block)
|
123
137
|
end
|
124
138
|
|
125
139
|
# if no filters, we use results to add profile
|
126
140
|
def results(profile=nil, options={})
|
141
|
+
options, profile = profile, nil if profile.is_a?(Hash)
|
142
|
+
|
127
143
|
self.profile = profile unless profile.nil?
|
128
144
|
apply_options(options)
|
129
145
|
self
|
@@ -145,8 +161,8 @@ module Legato
|
|
145
161
|
parent_klass.dimensions
|
146
162
|
end
|
147
163
|
|
148
|
-
def
|
149
|
-
@
|
164
|
+
def sort=(arr)
|
165
|
+
@sort = Legato::ListParameter.new(:sort, arr)
|
150
166
|
end
|
151
167
|
|
152
168
|
# def segment_id
|
@@ -165,10 +181,11 @@ module Legato
|
|
165
181
|
'max-results' => limit,
|
166
182
|
'start-index' => offset,
|
167
183
|
# 'segment' => segment_id,
|
168
|
-
'filters' => filters.to_params # defaults to AND filtering
|
184
|
+
'filters' => filters.to_params, # defaults to AND filtering
|
185
|
+
'fields' => REQUEST_FIELDS
|
169
186
|
}
|
170
187
|
|
171
|
-
[metrics, dimensions,
|
188
|
+
[metrics, dimensions, sort].each do |list|
|
172
189
|
params.merge!(list.to_params) unless list.nil?
|
173
190
|
end
|
174
191
|
|
data/lib/legato/request.rb
CHANGED
data/lib/legato/response.rb
CHANGED
@@ -6,13 +6,21 @@ module Legato
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def data
|
9
|
-
@data ||=
|
9
|
+
@data ||= MultiJson.decode(@raw_response.body)
|
10
10
|
end
|
11
11
|
|
12
12
|
def collection
|
13
13
|
raw_attributes.map {|attributes| @instance_klass.new(attributes)}
|
14
14
|
end
|
15
15
|
|
16
|
+
def total_results
|
17
|
+
data["totalResults"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def totals_for_all_results
|
21
|
+
Hash[data["totalsForAllResults"].map{|k,v| [Legato.from_ga_string(k), number_for(v)]}]
|
22
|
+
end
|
23
|
+
|
16
24
|
private
|
17
25
|
def headers
|
18
26
|
data['columnHeaders']
|
@@ -23,69 +31,16 @@ module Legato
|
|
23
31
|
end
|
24
32
|
|
25
33
|
def rows
|
26
|
-
data['rows']
|
34
|
+
Array.wrap(data['rows']).compact
|
27
35
|
end
|
28
36
|
|
29
37
|
def raw_attributes
|
30
38
|
rows.map {|row| Hash[fields.zip(row)]}
|
31
39
|
end
|
32
|
-
end
|
33
|
-
end
|
34
40
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
# @data = response_body
|
42
|
-
# @instance_klass = instance_klass
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# def results
|
46
|
-
# if @results.nil?
|
47
|
-
# @results = ResultSet.new(parse)
|
48
|
-
# @results.total_results = parse_total_results
|
49
|
-
# @results.sampled = parse_sampled_flag
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# @results
|
53
|
-
# end
|
54
|
-
#
|
55
|
-
# def sampled?
|
56
|
-
# end
|
57
|
-
#
|
58
|
-
# private
|
59
|
-
# def parse
|
60
|
-
# entries.map do |entry|
|
61
|
-
# @instance_klass.new(Hash[
|
62
|
-
# values_for(entry).map {|v| [Garb.from_ga(v['name']), v['value']]}
|
63
|
-
# ])
|
64
|
-
# end
|
65
|
-
# end
|
66
|
-
#
|
67
|
-
# def entries
|
68
|
-
# feed? ? [parsed_data['feed']['entry']].flatten.compact : []
|
69
|
-
# end
|
70
|
-
#
|
71
|
-
# def parse_total_results
|
72
|
-
# feed? ? parsed_data['feed']['openSearch:totalResults'].to_i : 0
|
73
|
-
# end
|
74
|
-
#
|
75
|
-
# def parse_sampled_flag
|
76
|
-
# feed? ? (parsed_data['feed']['dxp$containsSampledData'] == 'true') : false
|
77
|
-
# end
|
78
|
-
#
|
79
|
-
# def parsed_data
|
80
|
-
# @parsed_data ||= JSON.parse(@data)
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# def feed?
|
84
|
-
# !parsed_data['feed'].nil?
|
85
|
-
# end
|
86
|
-
#
|
87
|
-
# def values_for(entry)
|
88
|
-
# KEYS.map {|k| entry[k]}.flatten.compact
|
89
|
-
# end
|
90
|
-
# end
|
91
|
-
# end
|
41
|
+
def number_for(str)
|
42
|
+
return str.to_f if str.index('.')
|
43
|
+
str.to_i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/legato/version.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Legato::Filter do
|
4
4
|
context "a Filter instance" do
|
5
5
|
before :each do
|
6
|
-
@filter = Legato::Filter.new(:exits, :lt, 1000)
|
6
|
+
@filter = Legato::Filter.new(:exits, :lt, 1000, nil)
|
7
7
|
end
|
8
8
|
|
9
9
|
it 'has a field' do
|
@@ -28,8 +28,8 @@ describe Legato::Filter do
|
|
28
28
|
@filter.value.should == 1000
|
29
29
|
end
|
30
30
|
|
31
|
-
it 'has a default join character' do
|
32
|
-
@filter.join_character.should ==
|
31
|
+
it 'has a no default join character' do
|
32
|
+
@filter.join_character.should == nil
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'represents itself as a parameter' do
|
@@ -45,5 +45,15 @@ describe Legato::Filter do
|
|
45
45
|
it 'returns to_param if joining with nil' do
|
46
46
|
@filter.join_with(nil).should == @filter.to_param
|
47
47
|
end
|
48
|
+
|
49
|
+
it 'properly escapes commas' do
|
50
|
+
@filter.value = ","
|
51
|
+
@filter.escaped_value.should == "\\,"
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'properly escapes semicolons' do
|
55
|
+
@filter.value = ";"
|
56
|
+
@filter.escaped_value.should == "\\;"
|
57
|
+
end
|
48
58
|
end
|
49
59
|
end
|
@@ -10,10 +10,11 @@ describe Legato::Management::Profile do
|
|
10
10
|
|
11
11
|
it 'creates a new profile instance from a hash of attributes' do
|
12
12
|
user = stub
|
13
|
-
profile = Legato::Management::Profile.new({"id" => 12345, "name" => "Profile 1"}, user)
|
13
|
+
profile = Legato::Management::Profile.new({"id" => 12345, "name" => "Profile 1", "webPropertyId" => "WebProperty 7"}, user)
|
14
14
|
profile.user.should == user
|
15
15
|
profile.id.should == 12345
|
16
16
|
profile.name.should == "Profile 1"
|
17
|
+
profile.web_property_id.should == "WebProperty 7"
|
17
18
|
end
|
18
19
|
|
19
20
|
it 'returns an array of all profiles available to a user under an account' do
|
@@ -38,12 +38,12 @@ describe "Legato::Model" do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'creates a class method' do
|
41
|
-
@model.filter :high,
|
41
|
+
@model.filter :high, &@block
|
42
42
|
@model.respond_to?(:high).should be_true
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'stores the filter' do
|
46
|
-
@model.filter :high,
|
46
|
+
@model.filter :high, &@block
|
47
47
|
@model.filters[:high].should == @block
|
48
48
|
end
|
49
49
|
|
@@ -51,11 +51,11 @@ describe "Legato::Model" do
|
|
51
51
|
query = stub(:apply_filter => "a query")
|
52
52
|
Legato::Query.stubs(:new).returns(query)
|
53
53
|
|
54
|
-
@model.filter :high,
|
54
|
+
@model.filter :high, &@block
|
55
55
|
@model.high('arg1').should == 'a query'
|
56
56
|
|
57
57
|
Legato::Query.should have_received(:new).with(@model)
|
58
|
-
query.should have_received(:apply_filter).with('arg1'
|
58
|
+
query.should have_received(:apply_filter).with('arg1')
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -10,7 +10,7 @@ describe Legato::Query do
|
|
10
10
|
it "returns a new filter for #{operator} to the set" do
|
11
11
|
Legato::Filter.stubs(:new).returns("a filter")
|
12
12
|
filter = @query.send(operator, :key, 2000)
|
13
|
-
Legato::Filter.should have_received(:new).with(:key, operator, 2000)
|
13
|
+
Legato::Filter.should have_received(:new).with(:key, operator, 2000, nil)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -39,7 +39,7 @@ describe Legato::Query do
|
|
39
39
|
it "has filter methods that call apply with the given block" do
|
40
40
|
@query.stubs(:apply_filter)
|
41
41
|
@query.high('hi')
|
42
|
-
@query.should have_received(:apply_filter).with('hi'
|
42
|
+
@query.should have_received(:apply_filter).with('hi')
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'does not load results by default' do
|
@@ -47,7 +47,7 @@ describe Legato::Query do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it "loads a collection of results" do
|
50
|
-
response = stub(:collection => [])
|
50
|
+
response = stub(:collection => [], :total_results => 0, :totals_for_all_results => {})
|
51
51
|
user = stub(:request => response)
|
52
52
|
@query.stubs(:profile => stub(:user => user))
|
53
53
|
|
@@ -56,15 +56,29 @@ describe Legato::Query do
|
|
56
56
|
@query.loaded?.should be_true
|
57
57
|
@query.profile.user.should have_received(:request).with(@query)
|
58
58
|
response.should have_received(:collection)
|
59
|
+
response.should have_received(:total_results)
|
60
|
+
response.should have_received(:totals_for_all_results)
|
59
61
|
end
|
60
62
|
|
61
63
|
it "returns the collection" do
|
62
|
-
@query.stubs(:request_for_query).returns(stub(:collection => [1,2,3]))
|
64
|
+
@query.stubs(:request_for_query).returns(stub(:collection => [1,2,3], :total_results => 3, :totals_for_all_results => {'foo' => 34.2}))
|
63
65
|
@query.load
|
64
66
|
@query.collection.should == [1,2,3]
|
65
67
|
@query.to_a.should == [1,2,3]
|
66
68
|
end
|
67
69
|
|
70
|
+
it "returns the total number of results" do
|
71
|
+
@query.stubs(:request_for_query).returns(stub(:collection => [1,2,3], :total_results => 3, :totals_for_all_results => {'foo' => 34.2}))
|
72
|
+
@query.load
|
73
|
+
@query.total_results.should == 3
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns the totals for all results" do
|
77
|
+
@query.stubs(:request_for_query).returns(stub(:collection => [1,2,3], :total_results => 3, :totals_for_all_results => {'foo' => 34.2}))
|
78
|
+
@query.load
|
79
|
+
@query.totals_for_all_results.should == {'foo' => 34.2}
|
80
|
+
end
|
81
|
+
|
68
82
|
it "behaves like an enumerable delegating to the collection" do
|
69
83
|
collection = []
|
70
84
|
collection.stubs(:each)
|
@@ -100,9 +114,19 @@ describe Legato::Query do
|
|
100
114
|
@query.should have_received(:apply_options).with({})
|
101
115
|
end
|
102
116
|
|
117
|
+
it 'sets options and returns self' do
|
118
|
+
@query.stubs(:profile=)
|
119
|
+
@query.stubs(:apply_options)
|
120
|
+
|
121
|
+
@query.results({:sort => [:city]}).should == @query
|
122
|
+
|
123
|
+
@query.should have_received(:profile=).never
|
124
|
+
@query.should have_received(:apply_options).with({:sort => [:city]})
|
125
|
+
end
|
126
|
+
|
103
127
|
context 'when applying filters' do
|
104
128
|
before :each do
|
105
|
-
@filter = Legato::Filter.new(:key, :eql, 1000)
|
129
|
+
@filter = Legato::Filter.new(:key, :eql, 1000, nil)
|
106
130
|
@query.stubs(:eql).returns(@filter)
|
107
131
|
|
108
132
|
@filters = stub(:<<)
|
@@ -110,17 +134,17 @@ describe Legato::Query do
|
|
110
134
|
end
|
111
135
|
|
112
136
|
it 'returns the query' do
|
113
|
-
@query.apply_filter(
|
137
|
+
@query.apply_filter(&@block).should == @query
|
114
138
|
end
|
115
139
|
|
116
140
|
it 'executes the block' do
|
117
|
-
@query.apply_filter(
|
141
|
+
@query.apply_filter(&@block)
|
118
142
|
@query.should have_received(:eql).with(:key, 1000)
|
119
143
|
end
|
120
144
|
|
121
145
|
it 'accepts a profile as the first argument' do
|
122
146
|
profile = Legato::Management::Profile.new({}, stub)
|
123
|
-
@query.apply_filter(profile,
|
147
|
+
@query.apply_filter(profile, &@block)
|
124
148
|
@query.should have_received(:eql)
|
125
149
|
@query.profile.should == profile
|
126
150
|
end
|
@@ -128,7 +152,7 @@ describe Legato::Query do
|
|
128
152
|
it 'accepts a profile as the last argument' do
|
129
153
|
profile = Legato::Management::Profile.new({}, stub)
|
130
154
|
block_with_arg = lambda {|count| eql(:key, count)}
|
131
|
-
@query.apply_filter(100, profile, block_with_arg)
|
155
|
+
@query.apply_filter(100, profile, &block_with_arg)
|
132
156
|
@query.should have_received(:eql).with(:key, 100)
|
133
157
|
@query.profile.should == profile
|
134
158
|
end
|
@@ -136,12 +160,12 @@ describe Legato::Query do
|
|
136
160
|
it 'does not override the existing profile if none is provide' do
|
137
161
|
@query.profile = Legato::Management::Profile.new({}, stub)
|
138
162
|
block_with_arg = lambda {|count| eql(:key, count)}
|
139
|
-
@query.apply_filter(100, block_with_arg)
|
163
|
+
@query.apply_filter(100, &block_with_arg)
|
140
164
|
@query.profile.should_not == nil
|
141
165
|
end
|
142
166
|
|
143
167
|
it 'adds to the filter set' do
|
144
|
-
@query.apply_filter(
|
168
|
+
@query.apply_filter(&@block)
|
145
169
|
|
146
170
|
@filters.should have_received(:<<).with(@filter)
|
147
171
|
end
|
@@ -150,7 +174,7 @@ describe Legato::Query do
|
|
150
174
|
block = lambda {|*browsers| browsers.map {|browser| eql(:browser, browser)}}
|
151
175
|
@filter.stubs(:join_character=)
|
152
176
|
|
153
|
-
@query.apply_filter('chrome', 'safari', block)
|
177
|
+
@query.apply_filter('chrome', 'safari', &block)
|
154
178
|
|
155
179
|
@filter.should have_received(:join_character=).with(Legato.and_join_character)
|
156
180
|
@filter.should have_received(:join_character=).with(Legato.or_join_character)
|
@@ -162,26 +186,26 @@ describe Legato::Query do
|
|
162
186
|
@query.apply_options({}).should == @query
|
163
187
|
end
|
164
188
|
|
165
|
-
it "stores the
|
166
|
-
@query.apply_options({:
|
167
|
-
@query.
|
189
|
+
it "stores the sort" do
|
190
|
+
@query.apply_options({:sort => [:page_path]})
|
191
|
+
@query.sort.should == Legato::ListParameter.new(:sort, [:page_path])
|
168
192
|
end
|
169
193
|
|
170
|
-
it 'replaces the
|
171
|
-
@query.
|
172
|
-
@query.apply_options({:
|
173
|
-
@query.
|
194
|
+
it 'replaces the sort' do
|
195
|
+
@query.sort = [:pageviews]
|
196
|
+
@query.apply_options({:sort => [:page_path]})
|
197
|
+
@query.sort.should == Legato::ListParameter.new(:sort, [:page_path])
|
174
198
|
end
|
175
199
|
|
176
|
-
it "does not replace
|
177
|
-
@query.
|
200
|
+
it "does not replace sort if option is omitted" do
|
201
|
+
@query.sort = [:pageviews]
|
178
202
|
@query.apply_options({})
|
179
|
-
@query.
|
203
|
+
@query.sort.should == Legato::ListParameter.new(:sort, [:pageviews])
|
180
204
|
end
|
181
205
|
|
182
|
-
it "moves :sort option into
|
206
|
+
it "moves :sort option into sort" do
|
183
207
|
@query.apply_options({:sort => [:page_path]})
|
184
|
-
@query.
|
208
|
+
@query.sort.should == Legato::ListParameter.new(:sort, [:page_path])
|
185
209
|
end
|
186
210
|
|
187
211
|
it "sets the limit" do
|
@@ -265,6 +289,7 @@ describe Legato::Query do
|
|
265
289
|
@query.to_params.should == {
|
266
290
|
'ids' => 'ga:1234567890',
|
267
291
|
'start-date' => Legato.format_time(Time.now-Legato::Query::MONTH),
|
292
|
+
'fields' => Legato::Query::REQUEST_FIELDS,
|
268
293
|
'end-date' => Legato.format_time(Time.now)
|
269
294
|
}
|
270
295
|
end
|
@@ -273,7 +298,11 @@ describe Legato::Query do
|
|
273
298
|
now = Time.now
|
274
299
|
@query.start_date = now
|
275
300
|
@query.end_date = now
|
276
|
-
@query.to_params.should == {
|
301
|
+
@query.to_params.should == {
|
302
|
+
'start-date' => Legato.format_time(now),
|
303
|
+
'fields' => Legato::Query::REQUEST_FIELDS,
|
304
|
+
'end-date' => Legato.format_time(now)
|
305
|
+
}
|
277
306
|
end
|
278
307
|
|
279
308
|
it 'includes the limit' do
|
@@ -311,13 +340,13 @@ describe Legato::Query do
|
|
311
340
|
@query.to_params['dimensions'].should == 'browser,country'
|
312
341
|
end
|
313
342
|
|
314
|
-
it 'includes
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
@query.stubs(:
|
343
|
+
it 'includes sort' do
|
344
|
+
sort = Legato::ListParameter.new(:sort)
|
345
|
+
sort.stubs(:to_params).returns({'sort' => 'pageviews'})
|
346
|
+
sort.stubs(:empty?).returns(false)
|
347
|
+
@query.stubs(:sort).returns(sort)
|
319
348
|
|
320
|
-
@query.to_params['
|
349
|
+
@query.to_params['sort'].should == 'pageviews'
|
321
350
|
end
|
322
351
|
end
|
323
352
|
end
|
@@ -11,5 +11,18 @@ describe Legato::Response do
|
|
11
11
|
it 'has a collection of OpenStruct instances' do
|
12
12
|
@response.collection.first.should == OpenStruct.new({:browser=>"Android Browser", :pageviews=>"93"})
|
13
13
|
end
|
14
|
+
|
15
|
+
it 'has the number of total results' do
|
16
|
+
@response.total_results.should == 13
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has the totals for all results hash' do
|
20
|
+
@response.totals_for_all_results.should == {'pageviews' => 3710}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'handles no rows returned' do
|
24
|
+
@response.stubs(:data).returns({'rows' => nil})
|
25
|
+
@response.collection.should == []
|
26
|
+
end
|
14
27
|
end
|
15
|
-
end
|
28
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
shared_examples_for "a management finder" do
|
2
2
|
it "returns an array of all #{subject_class_name} available to a user" do
|
3
|
-
|
3
|
+
MultiJson.stubs(:decode).returns({'items' => ['item1', 'item2']})
|
4
4
|
response = stub(:body => 'some json')
|
5
5
|
access_token = stub(:get => response)
|
6
6
|
user = stub(:access_token => access_token)
|
@@ -11,7 +11,7 @@ shared_examples_for "a management finder" do
|
|
11
11
|
user.should have_received(:access_token)
|
12
12
|
access_token.should have_received(:get).with('https://www.googleapis.com/analytics/v3/management'+described_class.default_path)
|
13
13
|
response.should have_received(:body)
|
14
|
-
|
14
|
+
MultiJson.should have_received(:decode).with('some json')
|
15
15
|
described_class.should have_received(:new).with('item1', user)
|
16
16
|
described_class.should have_received(:new).with('item2', user)
|
17
17
|
end
|
metadata
CHANGED
@@ -1,98 +1,158 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: legato
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Tony Pitale
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
|
18
|
+
date: 2012-04-26 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
17
23
|
none: false
|
18
|
-
requirements:
|
19
|
-
- -
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 3
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
prerelease: false
|
22
32
|
type: :development
|
33
|
+
requirement: *id001
|
34
|
+
name: rake
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 3
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
23
45
|
prerelease: false
|
24
|
-
|
25
|
-
|
26
|
-
name:
|
27
|
-
|
46
|
+
type: :development
|
47
|
+
requirement: *id002
|
48
|
+
name: rspec
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
28
51
|
none: false
|
29
|
-
requirements:
|
30
|
-
- -
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
prerelease: false
|
33
60
|
type: :development
|
61
|
+
requirement: *id003
|
62
|
+
name: mocha
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
34
73
|
prerelease: false
|
35
|
-
|
36
|
-
|
74
|
+
type: :development
|
75
|
+
requirement: *id004
|
37
76
|
name: bourne
|
38
|
-
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
39
79
|
none: false
|
40
|
-
requirements:
|
41
|
-
- -
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
|
80
|
+
requirements:
|
81
|
+
- - "="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
hash: -161295594
|
84
|
+
segments:
|
85
|
+
- 2
|
86
|
+
- 0
|
87
|
+
- 0
|
88
|
+
- beta
|
89
|
+
- 2
|
90
|
+
version: 2.0.0.beta2
|
45
91
|
prerelease: false
|
46
|
-
|
47
|
-
|
92
|
+
type: :development
|
93
|
+
requirement: *id005
|
48
94
|
name: vcr
|
49
|
-
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
50
97
|
none: false
|
51
|
-
requirements:
|
52
|
-
- -
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
|
55
|
-
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
56
105
|
prerelease: false
|
57
|
-
|
58
|
-
|
106
|
+
type: :development
|
107
|
+
requirement: *id006
|
59
108
|
name: fakeweb
|
60
|
-
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
61
111
|
none: false
|
62
|
-
requirements:
|
63
|
-
- -
|
64
|
-
- !ruby/object:Gem::Version
|
65
|
-
|
66
|
-
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
67
119
|
prerelease: false
|
68
|
-
version_requirements: *70178713319120
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: oauth2
|
71
|
-
requirement: &70178713318020 !ruby/object:Gem::Requirement
|
72
|
-
none: false
|
73
|
-
requirements:
|
74
|
-
- - ! '>='
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: '0'
|
77
120
|
type: :runtime
|
121
|
+
requirement: *id007
|
122
|
+
name: multi_json
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
version_requirements: &id008 !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
78
133
|
prerelease: false
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
email:
|
134
|
+
type: :runtime
|
135
|
+
requirement: *id008
|
136
|
+
name: oauth2
|
137
|
+
description: Access the Google Analytics Core Reporting and Management APIs with Ruby. Create models for metrics and dimensions. Filter your data to tell you what you need.
|
138
|
+
email:
|
84
139
|
- tpitale@gmail.com
|
85
140
|
executables: []
|
141
|
+
|
86
142
|
extensions: []
|
143
|
+
|
87
144
|
extra_rdoc_files: []
|
88
|
-
|
145
|
+
|
146
|
+
files:
|
89
147
|
- .gitignore
|
90
148
|
- .rspec
|
149
|
+
- .travis.yml
|
91
150
|
- Gemfile
|
92
151
|
- README.md
|
93
152
|
- Rakefile
|
94
153
|
- legato.gemspec
|
95
154
|
- lib/legato.rb
|
155
|
+
- lib/legato/collection.rb
|
96
156
|
- lib/legato/core_ext/array.rb
|
97
157
|
- lib/legato/core_ext/string.rb
|
98
158
|
- lib/legato/filter.rb
|
@@ -130,37 +190,43 @@ files:
|
|
130
190
|
- spec/spec_helper.rb
|
131
191
|
- spec/support/examples/management_finder.rb
|
132
192
|
- spec/support/macros/oauth.rb
|
193
|
+
has_rdoc: true
|
133
194
|
homepage: http://github.com/tpitale/legato
|
134
195
|
licenses: []
|
196
|
+
|
135
197
|
post_install_message:
|
136
198
|
rdoc_options: []
|
137
|
-
|
199
|
+
|
200
|
+
require_paths:
|
138
201
|
- lib
|
139
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
202
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
203
|
none: false
|
141
|
-
requirements:
|
142
|
-
- -
|
143
|
-
- !ruby/object:Gem::Version
|
144
|
-
|
145
|
-
segments:
|
146
|
-
-
|
147
|
-
|
148
|
-
|
204
|
+
requirements:
|
205
|
+
- - ">="
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
hash: 55
|
208
|
+
segments:
|
209
|
+
- 1
|
210
|
+
- 9
|
211
|
+
- 2
|
212
|
+
version: 1.9.2
|
213
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
214
|
none: false
|
150
|
-
requirements:
|
151
|
-
- -
|
152
|
-
- !ruby/object:Gem::Version
|
153
|
-
|
154
|
-
segments:
|
215
|
+
requirements:
|
216
|
+
- - ">="
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
hash: 3
|
219
|
+
segments:
|
155
220
|
- 0
|
156
|
-
|
221
|
+
version: "0"
|
157
222
|
requirements: []
|
223
|
+
|
158
224
|
rubyforge_project: legato
|
159
|
-
rubygems_version: 1.
|
225
|
+
rubygems_version: 1.4.2
|
160
226
|
signing_key:
|
161
227
|
specification_version: 3
|
162
228
|
summary: Access the Google Analytics API with Ruby
|
163
|
-
test_files:
|
229
|
+
test_files:
|
164
230
|
- spec/cassettes/management/accounts.json
|
165
231
|
- spec/cassettes/management/profiles.json
|
166
232
|
- spec/cassettes/management/web_properties.json
|