Skip to content
Browse files

First attempt at code, so far untested in real environment.

  • Loading branch information...
1 parent 756845d commit 778feac9ea42ebc01a3282c6e5c4334c064aa234 @stuliston committed Nov 30, 2011
View
2 .rspec
@@ -0,0 +1,2 @@
+--color
+--format documentation
View
7 config/vcr_setup.rb
@@ -0,0 +1,7 @@
+require 'vcr'
+
+VCR.config do |c|
+ c.cassette_library_dir = 'vcr_cassettes'
+ c.stub_with :fakeweb
+ c.default_cassette_options = { :record => :once }
+end
View
3 garb-reporter.gemspec
@@ -16,6 +16,9 @@ Gem::Specification.new do |s|
# Development dependencies:
s.add_development_dependency "rspec", ">= 2.7.0"
+ s.add_development_dependency "i18n"
+ s.add_development_dependency "vcr"
+ s.add_development_dependency "fakeweb"
s.rubyforge_project = "garb-reporter"
View
4 lib/base_garb_report.rb
@@ -0,0 +1,4 @@
+module GarbReporter
+ class BaseGarbReport
+ end
+end
View
64 lib/report.rb
@@ -0,0 +1,64 @@
+require "garb-reporter/version"
+
+module GarbReporter
+
+ class Report
+
+ def initialize(profile)
+ @profile = profile
+ end
+
+ def method_missing(method, *args)
+
+ method_name = method.to_s
+ super unless valid_method_name?(method_name)
+
+ class_name = build_class_name(method_name)
+ klass = existing_report_class(class_name) || build_new_report_class(method_name, class_name)
+ klass.results(@profile)
+ end
+
+ private
+
+ def valid_method_name?(method_name)
+ (!method_name.empty? && method_name.scan('_by_').count <= 1 && !method_name.start_with?('_', 'by_', 'and_') && !method_name.end_with?('_', '_by', '_and'))
+ end
+
+ def existing_report_class(class_name)
+ class_exists?(class_name) ? GarbReporter.const_get(class_name) : nil
+ end
+
+ def class_exists?(class_name)
+ GarbReporter.const_defined?(class_name)
+ rescue NameError
+ return false
+ end
+
+ def build_new_report_class(method_name, class_name)
+ klass = GarbReporter.const_set(class_name, Class.new(GarbReporter::BaseGarbReport))
+ klass.extend Garb::Model
+ klass.metrics << extract_metrics(method_name)
+ klass.dimensions << extract_dimensions(method_name)
+ return klass
+ end
+
+ def build_class_name(method_name)
+ method_name.split('_').collect { |w| w.capitalize! }.join
+ end
+
+ def extract_metrics(method_name)
+ build_metrics_and_dimensions(method_name).first || []
+ end
+
+ def extract_dimensions(method_name)
+ return [] unless method_name.include?('_by_')
+
+ build_metrics_and_dimensions(method_name).last || []
+ end
+
+ def build_metrics_and_dimensions(method_name)
+ (metrics, dimensions) = method_name.split('_by_').collect { |part| part.split('_and_').collect(&:to_sym) }
+ end
+
+ end
+end
View
88 spec/garb_reporter_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe GarbReporter do
+
+ before do
+
+ # get VCR to serve us up a previously-canned http response for successful login
+ VCR.use_cassette('garb authentication') do
+ Garb::Session.login('stuart@example.com', 'password')
+ end
+
+ # get VCR to serve us up a previously-canned http response for fetching profiles
+ VCR.use_cassette('get profile') do
+ @profile = Garb::Management::Profile.all.detect { |p| p.web_property_id == 'UA-12345678-1' }
+ end
+
+ @report = GarbReporter::Report.new(@profile)
+ end
+
+ describe 'build_class_name' do
+
+ it 'should capitalise the string' do
+ @report.send(:build_class_name, 'visits').should == 'Visits'
+ end
+
+ it 'should strip out underscores' do
+ @report.send(:build_class_name, 'visits_by_office').should == 'VisitsByOffice'
+ end
+
+ it 'should handle really long strings' do
+ @report.send(:build_class_name, 'visits_and_pageviews_and_clicks_by_date_and_office').should == 'VisitsAndPageviewsAndClicksByDateAndOffice'
+ end
+ end
+
+ describe 'valid_method_name' do
+
+ it 'should not allow a blank string' do
+ @report.send(:valid_method_name?, '').should be_false
+ end
+
+ it 'should not allow more than one instance of "_by_"' do
+ @report.send(:valid_method_name?, 'visits_by_office_by_date').should be_false
+ end
+
+ it 'should not start or end with "_by_" or "_and_' do
+ @report.send(:valid_method_name?, '_by_date').should be_false
+ @report.send(:valid_method_name?, 'visits_by_').should be_false
+ @report.send(:valid_method_name?, '_and_date').should be_false
+ @report.send(:valid_method_name?, 'visits_and_').should be_false
+ end
+ end
+
+ describe 'extract_metrics' do
+
+ it 'should extract a single metric' do
+ @report.send(:extract_metrics, 'visits').should == [:visits]
+ end
+
+ it 'should extract multiple metrics' do
+ @report.send(:extract_metrics, 'visits_and_clicks_and_foo_and_bar').should == [:visits, :clicks, :foo, :bar]
+ end
+
+ it 'should ignore a dimension' do
+ @report.send(:extract_metrics, 'visits_and_clicks_by_date').should == [:visits, :clicks]
+ end
+
+ it 'should ignore multiple dimensions' do
+ @report.send(:extract_metrics, 'visits_and_clicks_by_date_and_office').should == [:visits, :clicks]
+ end
+ end
+
+ describe 'dimensions' do
+
+ it 'should know when there are no dimensions' do
+ @report.send(:extract_dimensions, 'visits').should == []
+ end
+
+ it 'should extract a single dimension' do
+ @report.send(:extract_dimensions, 'visits_by_date').should == [:date]
+ end
+
+ it 'should extract multiple dimensions' do
+ @report.send(:extract_dimensions, 'visits_by_date_and_office').should == [:date, :office]
+ end
+
+ end
+
+end
View
8 spec/spec_helper.rb
@@ -0,0 +1,8 @@
+require "config/vcr_setup"
+require "garb"
+require "base_garb_report"
+require "report"
+
+RSpec.configure do |config|
+ # some (optional) config here
+end
View
38 vcr_cassettes/garb_authentication.yml
@@ -0,0 +1,38 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :post
+ uri: https://www.google.com:443/accounts/ClientLogin
+ body: Passwd=password&service=analytics&accountType=HOSTED_OR_GOOGLE&Email=stuart%40example.com&source=vigetLabs-garb-001
+ headers:
+ content-type:
+ - application/x-www-form-urlencoded
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ x-content-type-options:
+ - nosniff
+ expires:
+ - Mon, 01-Jan-1990 00:00:00 GMT
+ content-type:
+ - text/plain
+ server:
+ - GSE
+ x-xss-protection:
+ - 1; mode=block
+ date:
+ - Wed, 30 Nov 2011 02:58:23 GMT
+ content-length:
+ - "881"
+ cache-control:
+ - no-cache, no-store
+ pragma:
+ - no-cache
+ body: |
+ SID=DQAAAMEAAABEPGo2a2ewm6JsKoF18TB1W4NzG21XMZZoX4uS44RP0BhBnP7daTnL3ZPtLO8fgEr-Ym-nF5OByN29mkdOSdFBA90zSMSlGedGyzryY_NC93C3zMFRe0Sa8ia-snWqbp7U_ja4u7v6mC7t4We_eyGRujR3tRJeCJURzYmcFLJtAuNPmkNWA8sZ1g-libvB6Y9Q_iSVyKWVU1te0vVBOamN1HPQizwDcNpEX4nKACiuHyt-Uj1fn1kTnS4HDbNirlQy2yjlt2klvnzy5GMPVkZN
+ LSID=DQAAAMIAAAB-o-KXnRZiasADsE59dnSkvovrXlY0htPFyvyeGZKIKumNGpatwtMVTogWlnGlRa0uZP4C3yyyOIXizXaogiYKlwKo3kEj7eo0QtnWjwXurwktNrfqUtdcoxjl8UkV_cIJl77aFcg5J5aQUJgv7d96jriTI5JB1W6krd3RjhqyjYICp5abqrMKASsUqjOaCBxI__5WvH4DYGQD4n64BrdCUUMFaGXOFDnpiPATWM9YvjQf7qDt0q_UkQify2tvlIJl70gvA-5dV-Ps1agz7Fpp
+ Auth=DQAAAMIAAAB-o-KXnRZiasADsE59dnSkvovrXlY0htPFyvyeGZKIKumNGpatwtMVTogWlnGlRa29p1ILfFYTDmueKnj9Ds2SOVu0tx670XjlgrRvtbia1MJ7p0M4uB5aJr4UMqqrkMF4dsnA28Les-plij6icPJq9OxCwQ2og_1q3bCjs-wkC6CN9lEBvQPeLj5ipIiCNM8fap03Qxfj7-0vTVFt4nP-kpYuIpILZja05N0am1_EAItaUOXWzcjPe_tDqSkXGN_5Mm46v7Qs2dR35ssEitsT
+
+ http_version: "1.1"
View
40 vcr_cassettes/get_profile.yml
@@ -0,0 +1,40 @@
+---
+- !ruby/struct:VCR::HTTPInteraction
+ request: !ruby/struct:VCR::Request
+ method: :get
+ uri: https://www.google.com:443/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles
+ body:
+ headers:
+ authorization:
+ - GoogleLogin auth=DQAAAMIAAAB-o-KXnRZiasADsE59dnSkvovrXlY0htPFyvyeGZKIKumNGpatwtMVTogWlnGlRa29p1ILfFYTDmueKnj9Ds2SOVu0tx670XjlgrRvtbia1MJ7p0M4uB5aJr4UMqqrkMF4dsnA28Les-plij6icPJq9OxCwQ2og_1q3bCjs-wkC6CN9lEBvQPeLj5ipIiCNM8fap03Qxfj7-0vTVFt4nP-kpYuIpILZja05N0am1_EAItaUOXWzcjPe_tDqSkXGN_5Mm46v7Qs2dR35ssEitsT
+ gdata-version:
+ - "2"
+ response: !ruby/struct:VCR::Response
+ status: !ruby/struct:VCR::ResponseStatus
+ code: 200
+ message: OK
+ headers:
+ x-frame-options:
+ - SAMEORIGIN
+ x-content-type-options:
+ - nosniff
+ last-modified:
+ - Wed, 30 Nov 2011 02:58:25 GMT
+ expires:
+ - Wed, 30 Nov 2011 02:58:25 GMT
+ content-type:
+ - application/atom+xml; charset=UTF-8; type=feed
+ server:
+ - GSE
+ x-xss-protection:
+ - 1; mode=block
+ date:
+ - Wed, 30 Nov 2011 02:58:25 GMT
+ gdata-version:
+ - "2.1"
+ vary:
+ - Accept, X-GData-Authorization, GData-Version
+ cache-control:
+ - private, max-age=0, must-revalidate, no-transform
+ 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>
+ http_version: "1.1"

0 comments on commit 778feac

Please sign in to comment.
Something went wrong with that request. Please try again.