garb-reporter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
6
+ notes
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create ruby-1.8.7-p352@garb-reporter
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in garb-reporter.gemspec
4
+ gemspec
data/README ADDED
@@ -0,0 +1 @@
1
+ Welcome to garb-reporter!
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require 'vcr'
2
+
3
+ VCR.config do |c|
4
+ c.cassette_library_dir = 'vcr_cassettes'
5
+ c.stub_with :fakeweb
6
+ c.default_cassette_options = { :record => :once }
7
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "garb-reporter/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "garb-reporter"
7
+ s.version = GarbReporter::VERSION
8
+ s.authors = ["Stu Liston"]
9
+ s.email = ["stuart.liston@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Provides a reporter class for Garb}
12
+ s.description = %q{GarbReporter removes the need in Garb to create a new class for every report by offering a querying api that dynamically creates the Garb classes for you in the background.}
13
+
14
+ # Only running dependency is Garb (and its dependencies)
15
+ s.add_dependency "garb", "0.9.1"
16
+
17
+ # Development dependencies:
18
+ s.add_development_dependency "rspec", ">= 2.7.0"
19
+ s.add_development_dependency "i18n"
20
+ s.add_development_dependency "vcr"
21
+ s.add_development_dependency "fakeweb"
22
+
23
+ s.rubyforge_project = "garb-reporter"
24
+
25
+ s.files = `git ls-files`.split("\n")
26
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
+ s.require_paths = ["lib"]
29
+
30
+ # specify any dependencies here; for example:
31
+ # s.add_development_dependency "rspec"
32
+ # s.add_runtime_dependency "rest-client"
33
+ end
@@ -0,0 +1,3 @@
1
+ module GarbReporter
2
+ VERSION = "0.0.1"
3
+ end
data/lib/report.rb ADDED
@@ -0,0 +1,64 @@
1
+ require "garb-reporter/version"
2
+
3
+ module GarbReporter
4
+
5
+ class Report
6
+
7
+ def initialize(profile)
8
+ @profile = profile
9
+ end
10
+
11
+ def method_missing(method, *args)
12
+
13
+ method_name = method.to_s
14
+ super unless valid_method_name?(method_name)
15
+
16
+ class_name = build_class_name(method_name)
17
+ klass = existing_report_class(class_name) || build_new_report_class(method_name, class_name)
18
+ klass.results(@profile)
19
+ end
20
+
21
+ private
22
+
23
+ def valid_method_name?(method_name)
24
+ (!method_name.empty? && method_name.scan('_by_').count <= 1 && !method_name.start_with?('_', 'by_', 'and_') && !method_name.end_with?('_', '_by', '_and'))
25
+ end
26
+
27
+ def existing_report_class(class_name)
28
+ class_exists?(class_name) ? GarbReporter.const_get(class_name) : nil
29
+ end
30
+
31
+ def class_exists?(class_name)
32
+ GarbReporter.const_defined?(class_name)
33
+ rescue NameError
34
+ return false
35
+ end
36
+
37
+ def build_new_report_class(method_name, class_name)
38
+ klass = GarbReporter.const_set(class_name, Class.new())
39
+ klass.extend Garb::Model
40
+ klass.metrics << extract_metrics(method_name)
41
+ klass.dimensions << extract_dimensions(method_name)
42
+ return klass
43
+ end
44
+
45
+ def build_class_name(method_name)
46
+ method_name.split('_').collect { |w| w.capitalize! }.join
47
+ end
48
+
49
+ def extract_metrics(method_name)
50
+ build_metrics_and_dimensions(method_name).first || []
51
+ end
52
+
53
+ def extract_dimensions(method_name)
54
+ return [] unless method_name.include?('_by_')
55
+
56
+ build_metrics_and_dimensions(method_name).last || []
57
+ end
58
+
59
+ def build_metrics_and_dimensions(method_name)
60
+ (metrics, dimensions) = method_name.split('_by_').collect { |part| part.split('_and_').collect(&:to_sym) }
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe GarbReporter do
4
+
5
+ before do
6
+
7
+ # get VCR to serve us up a previously-canned http response for successful login
8
+ VCR.use_cassette('garb authentication') do
9
+ Garb::Session.login('stuart@example.com', 'password')
10
+ end
11
+
12
+ # get VCR to serve us up a previously-canned http response for fetching profiles
13
+ VCR.use_cassette('get profile') do
14
+ @profile = Garb::Management::Profile.all.detect { |p| p.web_property_id == 'UA-12345678-1' }
15
+ end
16
+
17
+ @report = GarbReporter::Report.new(@profile)
18
+ end
19
+
20
+ describe 'build_class_name' do
21
+
22
+ it 'should capitalise the string' do
23
+ @report.send(:build_class_name, 'visits').should == 'Visits'
24
+ end
25
+
26
+ it 'should strip out underscores' do
27
+ @report.send(:build_class_name, 'visits_by_office').should_not match(/_/)
28
+ end
29
+
30
+ it 'should handle really long strings' do
31
+ @report.send(:build_class_name, 'visits_and_pageviews_and_clicks_by_date_and_office').should == 'VisitsAndPageviewsAndClicksByDateAndOffice'
32
+ end
33
+ end
34
+
35
+ describe 'valid_method_name' do
36
+
37
+ it 'should not allow a blank string' do
38
+ @report.send(:valid_method_name?, '').should be_false
39
+ end
40
+
41
+ it 'should not allow more than one instance of "_by_"' do
42
+ @report.send(:valid_method_name?, 'visits_by_office_by_date').should be_false
43
+ end
44
+
45
+ it 'should not start or end with "_by_" or "_and_' do
46
+ @report.send(:valid_method_name?, '_by_date').should be_false
47
+ @report.send(:valid_method_name?, 'visits_by_').should be_false
48
+ @report.send(:valid_method_name?, '_and_date').should be_false
49
+ @report.send(:valid_method_name?, 'visits_and_').should be_false
50
+ end
51
+ end
52
+
53
+ describe 'extract_metrics' do
54
+
55
+ it 'should extract a single metric' do
56
+ @report.send(:extract_metrics, 'visits').should == [:visits]
57
+ end
58
+
59
+ it 'should extract multiple metrics' do
60
+ @report.send(:extract_metrics, 'visits_and_clicks_and_foo_and_bar').should == [:visits, :clicks, :foo, :bar]
61
+ end
62
+
63
+ it 'should ignore a dimension' do
64
+ @report.send(:extract_metrics, 'visits_and_clicks_by_date').should == [:visits, :clicks]
65
+ end
66
+
67
+ it 'should ignore multiple dimensions' do
68
+ @report.send(:extract_metrics, 'visits_and_clicks_by_date_and_office').should == [:visits, :clicks]
69
+ end
70
+ end
71
+
72
+ describe 'dimensions' do
73
+
74
+ it 'should know when there are no dimensions' do
75
+ @report.send(:extract_dimensions, 'visits').should == []
76
+ end
77
+
78
+ it 'should extract a single dimension' do
79
+ @report.send(:extract_dimensions, 'visits_by_date').should == [:date]
80
+ end
81
+
82
+ it 'should extract multiple dimensions' do
83
+ @report.send(:extract_dimensions, 'visits_by_date_and_office').should == [:date, :office]
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,7 @@
1
+ require "config/vcr_setup"
2
+ require "garb"
3
+ require "report"
4
+
5
+ RSpec.configure do |config|
6
+ # some (optional) config here
7
+ end
@@ -0,0 +1,38 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :post
5
+ uri: https://www.google.com:443/accounts/ClientLogin
6
+ body: Passwd=password&service=analytics&accountType=HOSTED_OR_GOOGLE&Email=stuart%40example.com&source=vigetLabs-garb-001
7
+ headers:
8
+ content-type:
9
+ - application/x-www-form-urlencoded
10
+ response: !ruby/struct:VCR::Response
11
+ status: !ruby/struct:VCR::ResponseStatus
12
+ code: 200
13
+ message: OK
14
+ headers:
15
+ x-content-type-options:
16
+ - nosniff
17
+ expires:
18
+ - Mon, 01-Jan-1990 00:00:00 GMT
19
+ content-type:
20
+ - text/plain
21
+ server:
22
+ - GSE
23
+ x-xss-protection:
24
+ - 1; mode=block
25
+ date:
26
+ - Wed, 30 Nov 2011 02:58:23 GMT
27
+ content-length:
28
+ - "881"
29
+ cache-control:
30
+ - no-cache, no-store
31
+ pragma:
32
+ - no-cache
33
+ body: |
34
+ SID=DQAAAMEAAABEPGo2a2ewm6JsKoF18TB1W4NzG21XMZZoX4uS44RP0BhBnP7daTnL3ZPtLO8fgEr-Ym-nF5OByN29mkdOSdFBA90zSMSlGedGyzryY_NC93C3zMFRe0Sa8ia-snWqbp7U_ja4u7v6mC7t4We_eyGRujR3tRJeCJURzYmcFLJtAuNPmkNWA8sZ1g-libvB6Y9Q_iSVyKWVU1te0vVBOamN1HPQizwDcNpEX4nKACiuHyt-Uj1fn1kTnS4HDbNirlQy2yjlt2klvnzy5GMPVkZN
35
+ LSID=DQAAAMIAAAB-o-KXnRZiasADsE59dnSkvovrXlY0htPFyvyeGZKIKumNGpatwtMVTogWlnGlRa0uZP4C3yyyOIXizXaogiYKlwKo3kEj7eo0QtnWjwXurwktNrfqUtdcoxjl8UkV_cIJl77aFcg5J5aQUJgv7d96jriTI5JB1W6krd3RjhqyjYICp5abqrMKASsUqjOaCBxI__5WvH4DYGQD4n64BrdCUUMFaGXOFDnpiPATWM9YvjQf7qDt0q_UkQify2tvlIJl70gvA-5dV-Ps1agz7Fpp
36
+ Auth=DQAAAMIAAAB-o-KXnRZiasADsE59dnSkvovrXlY0htPFyvyeGZKIKumNGpatwtMVTogWlnGlRa29p1ILfFYTDmueKnj9Ds2SOVu0tx670XjlgrRvtbia1MJ7p0M4uB5aJr4UMqqrkMF4dsnA28Les-plij6icPJq9OxCwQ2og_1q3bCjs-wkC6CN9lEBvQPeLj5ipIiCNM8fap03Qxfj7-0vTVFt4nP-kpYuIpILZja05N0am1_EAItaUOXWzcjPe_tDqSkXGN_5Mm46v7Qs2dR35ssEitsT
37
+
38
+ http_version: "1.1"
@@ -0,0 +1,40 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: https://www.google.com:443/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles
6
+ body:
7
+ headers:
8
+ authorization:
9
+ - GoogleLogin auth=DQAAAMIAAAB-o-KXnRZiasADsE59dnSkvovrXlY0htPFyvyeGZKIKumNGpatwtMVTogWlnGlRa29p1ILfFYTDmueKnj9Ds2SOVu0tx670XjlgrRvtbia1MJ7p0M4uB5aJr4UMqqrkMF4dsnA28Les-plij6icPJq9OxCwQ2og_1q3bCjs-wkC6CN9lEBvQPeLj5ipIiCNM8fap03Qxfj7-0vTVFt4nP-kpYuIpILZja05N0am1_EAItaUOXWzcjPe_tDqSkXGN_5Mm46v7Qs2dR35ssEitsT
10
+ gdata-version:
11
+ - "2"
12
+ response: !ruby/struct:VCR::Response
13
+ status: !ruby/struct:VCR::ResponseStatus
14
+ code: 200
15
+ message: OK
16
+ headers:
17
+ x-frame-options:
18
+ - SAMEORIGIN
19
+ x-content-type-options:
20
+ - nosniff
21
+ last-modified:
22
+ - Wed, 30 Nov 2011 02:58:25 GMT
23
+ expires:
24
+ - Wed, 30 Nov 2011 02:58:25 GMT
25
+ content-type:
26
+ - application/atom+xml; charset=UTF-8; type=feed
27
+ server:
28
+ - GSE
29
+ x-xss-protection:
30
+ - 1; mode=block
31
+ date:
32
+ - Wed, 30 Nov 2011 02:58:25 GMT
33
+ gdata-version:
34
+ - "2.1"
35
+ vary:
36
+ - Accept, X-GData-Authorization, GData-Version
37
+ cache-control:
38
+ - private, max-age=0, must-revalidate, no-transform
39
+ body: <?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:dxp="http://schemas.google.com/analytics/2009" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gd="http://schemas.google.com/g/2005" gd:kind="analytics#profiles"> <id>https://www.google.com/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles</id> <updated>2011-11-30T02:58:25.381Z</updated> <title>Google Analytics Profiles for stuart@example.com</title> <link rel="self" type="application/atom+xml" href="https://www.google.com/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles"/> <author> <name>Google Analytics</name> </author> <generator version="1.0">Google Analytics</generator> <openSearch:totalResults>53</openSearch:totalResults> <openSearch:startIndex>1</openSearch:startIndex> <openSearch:itemsPerPage>1000</openSearch:itemsPerPage> <entry gd:etag="W/&quot;C0cHSXw7fSp7I2A9WhRREUw.&quot;" gd:kind="analytics#profile"> <id>https://www.google.com/analytics/feeds/datasources/ga/accounts/12345678/webproperties/UA-12345678-1/profiles/67976579</id> <updated>2011-11-23T21:03:58.205-08:00</updated> <title>Google Analytics Profile TEST</title> <link rel="self" type="application/atom+xml" href="https://www.google.com/analytics/feeds/datasources/ga/accounts/12345678/webproperties/UA-12345678-1/profiles/67976579"/> <link rel="http://schemas.google.com/ga/2009#parent" type="application/atom+xml" href="https://www.google.com/analytics/feeds/datasources/ga/accounts/12345678/webproperties/UA-12345678-1" gd:targetKind="analytics#webproperty"/> <link rel="http://schemas.google.com/ga/2009#child" type="application/atom+xml" href="https://www.google.com/analytics/feeds/datasources/ga/accounts/12345678/webproperties/UA-12345678-1/profiles/67976579/goals" gd:targetKind="analytics#goals"/> <dxp:property name="ga:accountId" value="12345678"/> <dxp:property name="ga:webPropertyId" value="UA-12345678-1"/> <dxp:property name="ga:profileName" value="CAV UAT"/> <dxp:property name="ga:profileId" value="67976579"/> <dxp:property name="dxp:tableId" value="ga:67976579"/> <dxp:property name="ga:currency" value="USD"/> <dxp:property name="ga:timezone" value="America/Los_Angeles"/> </entry></feed>
40
+ http_version: "1.1"
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: garb-reporter
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Stu Liston
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-11-30 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: garb
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - "="
27
+ - !ruby/object:Gem::Version
28
+ hash: 57
29
+ segments:
30
+ - 0
31
+ - 9
32
+ - 1
33
+ version: 0.9.1
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 19
45
+ segments:
46
+ - 2
47
+ - 7
48
+ - 0
49
+ version: 2.7.0
50
+ type: :development
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: i18n
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :development
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: vcr
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :development
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: fakeweb
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ type: :development
93
+ version_requirements: *id005
94
+ description: GarbReporter removes the need in Garb to create a new class for every report by offering a querying api that dynamically creates the Garb classes for you in the background.
95
+ email:
96
+ - stuart.liston@gmail.com
97
+ executables: []
98
+
99
+ extensions: []
100
+
101
+ extra_rdoc_files: []
102
+
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - .rvmrc
107
+ - Gemfile
108
+ - README
109
+ - Rakefile
110
+ - config/vcr_setup.rb
111
+ - garb-reporter.gemspec
112
+ - lib/garb-reporter/version.rb
113
+ - lib/report.rb
114
+ - spec/garb_reporter_spec.rb
115
+ - spec/spec_helper.rb
116
+ - vcr_cassettes/garb_authentication.yml
117
+ - vcr_cassettes/get_profile.yml
118
+ homepage: ""
119
+ licenses: []
120
+
121
+ post_install_message:
122
+ rdoc_options: []
123
+
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ hash: 3
132
+ segments:
133
+ - 0
134
+ version: "0"
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 3
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ requirements: []
145
+
146
+ rubyforge_project: garb-reporter
147
+ rubygems_version: 1.8.10
148
+ signing_key:
149
+ specification_version: 3
150
+ summary: Provides a reporter class for Garb
151
+ test_files:
152
+ - spec/garb_reporter_spec.rb
153
+ - spec/spec_helper.rb