garb-no-activesupport 0.7.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.
- data/README.md +250 -0
- data/Rakefile +55 -0
- data/lib/garb/account.rb +29 -0
- data/lib/garb/authentication_request.rb +53 -0
- data/lib/garb/data_request.rb +42 -0
- data/lib/garb/filter_parameters.rb +41 -0
- data/lib/garb/profile.rb +57 -0
- data/lib/garb/profile_reports.rb +15 -0
- data/lib/garb/report.rb +26 -0
- data/lib/garb/report_parameter.rb +25 -0
- data/lib/garb/report_response.rb +62 -0
- data/lib/garb/reports/bounces.rb +5 -0
- data/lib/garb/reports/exits.rb +5 -0
- data/lib/garb/reports/pageviews.rb +5 -0
- data/lib/garb/reports/unique_pageviews.rb +5 -0
- data/lib/garb/reports/visits.rb +5 -0
- data/lib/garb/reports.rb +5 -0
- data/lib/garb/resource.rb +92 -0
- data/lib/garb/session.rb +25 -0
- data/lib/garb/version.rb +13 -0
- data/lib/garb.rb +38 -0
- data/lib/string_ext.rb +20 -0
- data/lib/support.rb +39 -0
- data/test/fixtures/cacert.pem +67 -0
- data/test/fixtures/profile_feed.xml +40 -0
- data/test/fixtures/report_feed.xml +46 -0
- data/test/test_helper.rb +18 -0
- data/test/unit/garb/account_test.rb +53 -0
- data/test/unit/garb/authentication_request_test.rb +121 -0
- data/test/unit/garb/data_request_test.rb +106 -0
- data/test/unit/garb/filter_parameters_test.rb +59 -0
- data/test/unit/garb/oauth_session_test.rb +11 -0
- data/test/unit/garb/profile_reports_test.rb +29 -0
- data/test/unit/garb/profile_test.rb +87 -0
- data/test/unit/garb/report_parameter_test.rb +43 -0
- data/test/unit/garb/report_response_test.rb +29 -0
- data/test/unit/garb/report_test.rb +91 -0
- data/test/unit/garb/resource_test.rb +38 -0
- data/test/unit/garb/session_test.rb +84 -0
- data/test/unit/garb_test.rb +14 -0
- data/test/unit/symbol_operator_test.rb +37 -0
- metadata +133 -0
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,250 @@ | |
| 1 | 
            +
            Garb
         | 
| 2 | 
            +
            ====
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              http://github.com/vigetlabs/garb
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Important Changes
         | 
| 7 | 
            +
            =================
         | 
| 8 | 
            +
             | 
| 9 | 
            +
             Please read CHANGELOG
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Description
         | 
| 12 | 
            +
            -----------
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              Provides a Ruby API to the Google Analytics API.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              http://code.google.com/apis/analytics/docs/gdata/gdataDeveloperGuide.html
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            Basic Usage
         | 
| 19 | 
            +
            ===========
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            Single User Login
         | 
| 22 | 
            +
            -----------------
         | 
| 23 | 
            +
              
         | 
| 24 | 
            +
                > Garb::Session.login(username, password)
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
            OAuth Access Token
         | 
| 27 | 
            +
            ------------------
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                > Garb::Session.access_token = access_token # assign from oauth gem
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            Accounts
         | 
| 32 | 
            +
            --------
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                > Garb::Account.all
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            Profiles
         | 
| 37 | 
            +
            --------
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                > Garb::Account.first.profiles
         | 
| 40 | 
            +
                
         | 
| 41 | 
            +
                > Garb::Profile.first('UA-XXXX-XX')
         | 
| 42 | 
            +
                
         | 
| 43 | 
            +
                > Garb::Profile.all
         | 
| 44 | 
            +
                > profile = Garb::Profile.all.first
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            Define a Report Class
         | 
| 47 | 
            +
            ---------------------
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                class Exits
         | 
| 50 | 
            +
                  extend Garb::Resource
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  metrics :exits, :pageviews, :exit_rate
         | 
| 53 | 
            +
                  dimensions :page_path
         | 
| 54 | 
            +
                  sort :exits
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  filters do
         | 
| 57 | 
            +
                    eql(:page_path, 'season')
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  # alternative:
         | 
| 61 | 
            +
                  # filters :page_path.eql => 10
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            Get the Results
         | 
| 65 | 
            +
            ---------------
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                > Exits.results(profile)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              OR shorthand
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                > profile.exits
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            Other Parameters
         | 
| 74 | 
            +
            ----------------
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              * start_date: The date of the period you would like this report to start
         | 
| 77 | 
            +
              * end_date: The date to end, inclusive
         | 
| 78 | 
            +
              * limit: The maximum number of results to be returned
         | 
| 79 | 
            +
              * offset: The starting index
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            Metrics & Dimensions
         | 
| 82 | 
            +
            --------------------
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              Metrics and Dimensions are very complex because of the ways in which the can and cannot be combined.
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              I suggest reading the google documentation to familiarize yourself with this.
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              http://code.google.com/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html#bounceRate
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              When you've returned, you can pass the appropriate combinations (up to 50 metrics and 2 dimenstions)
         | 
| 91 | 
            +
              to garb, as an array, of symbols. Or you can simply push a symbol into the array.
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            Sorting
         | 
| 94 | 
            +
            -------
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              Sorting can be done on any metric or dimension defined in the request, with .desc reversing the sort.
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            Building a Report
         | 
| 99 | 
            +
            -----------------
         | 
| 100 | 
            +
             | 
| 101 | 
            +
              Given the class, session, and profile from above we can do:
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                Exits.results(profile, :limit => 10, :offset => 19)
         | 
| 104 | 
            +
             | 
| 105 | 
            +
              Or, with sorting and filters:
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                Exits.results(profile, :limit => 10, :offset => 19) do
         | 
| 108 | 
            +
                  sort :exits
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  filters do
         | 
| 111 | 
            +
                    contains(:page_path, 'season')
         | 
| 112 | 
            +
                    gt(:exits, 100)
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  # or with a hash
         | 
| 116 | 
            +
                  # filters :page_path.contains => 'season', :exits.gt => 100
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
              reports will be an array of OpenStructs with methods for the metrics and dimensions returned.
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            Build a One-Off Report
         | 
| 122 | 
            +
            ----------------------
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                report = Garb::Report.new(profile)
         | 
| 125 | 
            +
                report.metrics :pageviews, :exits
         | 
| 126 | 
            +
                report.dimensions :page_path
         | 
| 127 | 
            +
                report.sort :exits
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                report.filters do
         | 
| 130 | 
            +
                  contains(:page_path, 'season')
         | 
| 131 | 
            +
                  gte(:exits, 10)
         | 
| 132 | 
            +
                and
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                # or with a hash
         | 
| 135 | 
            +
                # report.filters :page_path.contains => 'season', :exits.gt => 100
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                report.results
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            Filtering
         | 
| 140 | 
            +
            ---------
         | 
| 141 | 
            +
             | 
| 142 | 
            +
              Google Analytics supports a significant number of filtering options.
         | 
| 143 | 
            +
             | 
| 144 | 
            +
              http://code.google.com/apis/analytics/docs/gdata/gdataReference.html#filtering
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              We handle filtering as an array of hashes that you can push into, 
         | 
| 147 | 
            +
              which will be joined together (AND'd)
         | 
| 148 | 
            +
             | 
| 149 | 
            +
              Here is what we can do currently:
         | 
| 150 | 
            +
              (the operator is a method on a symbol metric or dimension)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              Operators on metrics:
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                eql => '==',
         | 
| 155 | 
            +
                not_eql => '!=',
         | 
| 156 | 
            +
                gt => '>',
         | 
| 157 | 
            +
                gte => '>=',
         | 
| 158 | 
            +
                lt => '<',
         | 
| 159 | 
            +
                lte => '<='
         | 
| 160 | 
            +
             | 
| 161 | 
            +
              Operators on dimensions:
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                matches => '==',
         | 
| 164 | 
            +
                does_not_match => '!=',
         | 
| 165 | 
            +
                contains => '=~',
         | 
| 166 | 
            +
                does_not_contain => '!~',
         | 
| 167 | 
            +
                substring => '=@',
         | 
| 168 | 
            +
                not_substring => '!@'
         | 
| 169 | 
            +
                
         | 
| 170 | 
            +
              Given the previous example one-off report, we can add a line for filter:
         | 
| 171 | 
            +
              
         | 
| 172 | 
            +
                report.filters do
         | 
| 173 | 
            +
                  eql(:page_path, '/extend/effectively-using-git-with-subversion/')
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
              Or, if you're comfortable using symbol operators:
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                report.filters :page_path.eql => '/extend/effectively-using-git-with-subversion/'
         | 
| 179 | 
            +
             | 
| 180 | 
            +
            SSL
         | 
| 181 | 
            +
            ---
         | 
| 182 | 
            +
             | 
| 183 | 
            +
              Version 0.2.3 includes support for real ssl encryption for SINGLE USER authentication. First do:
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                Garb::Session.login(username, password, :secure => true)
         | 
| 186 | 
            +
             | 
| 187 | 
            +
              Next, be sure to download http://curl.haxx.se/ca/cacert.pem into your application somewhere.
         | 
| 188 | 
            +
              Then, define a constant CA_CERT_FILE and point to that file.
         | 
| 189 | 
            +
             | 
| 190 | 
            +
              For whatever reason, simply creating a new certificate store and setting the defaults would
         | 
| 191 | 
            +
              not validate the google ssl certificate as authentic.
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            TODOS
         | 
| 194 | 
            +
            -----
         | 
| 195 | 
            +
             | 
| 196 | 
            +
              * Read opensearch header in results
         | 
| 197 | 
            +
              * Investigate new features from GA to see if they're in the API, implement if so
         | 
| 198 | 
            +
              * clarify AND/OR filtering behavior in code and documentation
         | 
| 199 | 
            +
             | 
| 200 | 
            +
            Requirements
         | 
| 201 | 
            +
            ------------
         | 
| 202 | 
            +
             | 
| 203 | 
            +
              * happymapper >= 0.3.0
         | 
| 204 | 
            +
              * active_support >= 2.2.0
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            Requirements for Testing
         | 
| 207 | 
            +
            ------------------------
         | 
| 208 | 
            +
             | 
| 209 | 
            +
              * jferris-mocha
         | 
| 210 | 
            +
              * tpitale-shoulda (works with minitest)
         | 
| 211 | 
            +
             | 
| 212 | 
            +
            Install
         | 
| 213 | 
            +
            -------
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                sudo gem install garb
         | 
| 216 | 
            +
             | 
| 217 | 
            +
            Contributors
         | 
| 218 | 
            +
            ------------
         | 
| 219 | 
            +
             | 
| 220 | 
            +
              Many Thanks, for all their help, goes to:
         | 
| 221 | 
            +
             | 
| 222 | 
            +
              * Patrick Reagan
         | 
| 223 | 
            +
              * Justin Marney
         | 
| 224 | 
            +
              * Nick Plante
         | 
| 225 | 
            +
             | 
| 226 | 
            +
            License
         | 
| 227 | 
            +
            -------
         | 
| 228 | 
            +
             | 
| 229 | 
            +
              (The MIT License)
         | 
| 230 | 
            +
             | 
| 231 | 
            +
              Copyright (c) 2010 Viget Labs
         | 
| 232 | 
            +
             | 
| 233 | 
            +
              Permission is hereby granted, free of charge, to any person obtaining
         | 
| 234 | 
            +
              a copy of this software and associated documentation files (the
         | 
| 235 | 
            +
              'Software'), to deal in the Software without restriction, including
         | 
| 236 | 
            +
              without limitation the rights to use, copy, modify, merge, publish,
         | 
| 237 | 
            +
              distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 238 | 
            +
              permit persons to whom the Software is furnished to do so, subject to
         | 
| 239 | 
            +
              the following conditions:
         | 
| 240 | 
            +
             | 
| 241 | 
            +
              The above copyright notice and this permission notice shall be
         | 
| 242 | 
            +
              included in all copies or substantial portions of the Software.
         | 
| 243 | 
            +
             | 
| 244 | 
            +
              THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
         | 
| 245 | 
            +
              EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 246 | 
            +
              MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
         | 
| 247 | 
            +
              IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
         | 
| 248 | 
            +
              CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
         | 
| 249 | 
            +
              TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
         | 
| 250 | 
            +
              SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            require 'rake/gempackagetask'
         | 
| 3 | 
            +
            require 'rake/testtask'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'lib/garb/version'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            task :default => :test
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            spec = Gem::Specification.new do |s|
         | 
| 10 | 
            +
              s.name              = 'garb-no-activesupport'
         | 
| 11 | 
            +
              s.version           = Garb::Version.to_s
         | 
| 12 | 
            +
              s.has_rdoc          = false
         | 
| 13 | 
            +
              s.rubyforge_project = 'viget'
         | 
| 14 | 
            +
              s.summary           = "Google Analytics API Ruby Wrapper"
         | 
| 15 | 
            +
              s.authors           = ['Tony Pitale', 'jonah honeyman']
         | 
| 16 | 
            +
              s.email             = 'gewglestolemypass@gmail.com'
         | 
| 17 | 
            +
              s.homepage          = 'http://github.com/jonuts/garb'
         | 
| 18 | 
            +
              s.files             = %w(README.md Rakefile) + Dir.glob("lib/**/*")
         | 
| 19 | 
            +
              s.test_files        = Dir.glob("test/**/*")
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              s.add_dependency("happymapper", [">= 0.3.0"])
         | 
| 22 | 
            +
            end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            Rake::GemPackageTask.new(spec) do |pkg|
         | 
| 25 | 
            +
              pkg.gem_spec = spec
         | 
| 26 | 
            +
            end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            Rake::TestTask.new do |t|
         | 
| 29 | 
            +
              t.libs << 'test'
         | 
| 30 | 
            +
              t.test_files = FileList["test/**/*_test.rb"]
         | 
| 31 | 
            +
              t.verbose = true
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            desc 'Generate the gemspec to serve this Gem from Github'
         | 
| 35 | 
            +
            task :github do
         | 
| 36 | 
            +
              file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
         | 
| 37 | 
            +
              File.open(file, 'w') {|f| f << spec.to_ruby }
         | 
| 38 | 
            +
              puts "Created gemspec: #{file}"
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            begin
         | 
| 42 | 
            +
              require 'rcov/rcovtask'
         | 
| 43 | 
            +
              
         | 
| 44 | 
            +
              desc "Generate RCov coverage report"
         | 
| 45 | 
            +
              Rcov::RcovTask.new(:rcov) do |t|
         | 
| 46 | 
            +
                t.test_files = FileList['test/**/*_test.rb']
         | 
| 47 | 
            +
                t.rcov_opts << "-x lib/garb.rb -x lib/garb/version.rb"
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            rescue LoadError
         | 
| 50 | 
            +
              nil
         | 
| 51 | 
            +
            end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            task :default => 'test'
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            # EOF
         | 
    
        data/lib/garb/account.rb
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              class Account
         | 
| 3 | 
            +
                attr_reader :id, :name, :profiles
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(profiles)
         | 
| 6 | 
            +
                  @id = profiles.first.account_id    
         | 
| 7 | 
            +
                  @name = profiles.first.account_name
         | 
| 8 | 
            +
                  @profiles = profiles
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.all(session = Session)
         | 
| 12 | 
            +
                  # Profile.all.group_to_array{|p| p.account_id}.map{|profiles| new(profiles)}
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  profile_groups = Profile.all(session).inject({}) do |hash, profile|
         | 
| 15 | 
            +
                    key = profile.account_id
         | 
| 16 | 
            +
                    
         | 
| 17 | 
            +
                    if hash.has_key?(key)
         | 
| 18 | 
            +
                      hash[key] << profile
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      hash[key] = [profile]
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    hash
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  profile_groups.map {|k,v| v}.map {|profiles| new(profiles)}
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              class AuthenticationRequest
         | 
| 3 | 
            +
                class AuthError < StandardError;end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                URL = 'https://www.google.com/accounts/ClientLogin'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(email, password, opts={})
         | 
| 8 | 
            +
                  @email = email
         | 
| 9 | 
            +
                  @password = password
         | 
| 10 | 
            +
                  @account_type = opts.fetch(:account_type, 'HOSTED_OR_GOOGLE')
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def parameters
         | 
| 14 | 
            +
                  {
         | 
| 15 | 
            +
                    'Email'       => @email,
         | 
| 16 | 
            +
                    'Passwd'      => @password,
         | 
| 17 | 
            +
                    'accountType' => @account_type,
         | 
| 18 | 
            +
                    'service'     => 'analytics',
         | 
| 19 | 
            +
                    'source'      => 'vigetLabs-garb-001'
         | 
| 20 | 
            +
                  }
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                def uri
         | 
| 24 | 
            +
                  URI.parse(URL)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def send_request(ssl_mode)
         | 
| 28 | 
            +
                  http = Net::HTTP.new(uri.host, uri.port)
         | 
| 29 | 
            +
                  http.use_ssl = true
         | 
| 30 | 
            +
                  http.verify_mode = ssl_mode
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  if ssl_mode == OpenSSL::SSL::VERIFY_PEER
         | 
| 33 | 
            +
                    http.ca_file = CA_CERT_FILE
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  http.request(build_request) do |response|
         | 
| 37 | 
            +
                    raise AuthError unless response.is_a?(Net::HTTPOK)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def build_request
         | 
| 42 | 
            +
                  post = Net::HTTP::Post.new(uri.path)
         | 
| 43 | 
            +
                  post.set_form_data(parameters)
         | 
| 44 | 
            +
                  post
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                
         | 
| 47 | 
            +
                def auth_token(opts={})
         | 
| 48 | 
            +
                  ssl_mode = opts[:secure] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
         | 
| 49 | 
            +
                  send_request(ssl_mode).body.match(/^Auth=(.*)$/)[1]
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              class DataRequest
         | 
| 3 | 
            +
                class ClientError < StandardError; end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(session, base_url, parameters={})
         | 
| 6 | 
            +
                  @session = session
         | 
| 7 | 
            +
                  @base_url = base_url
         | 
| 8 | 
            +
                  @parameters = parameters
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def query_string
         | 
| 12 | 
            +
                  parameter_list = @parameters.map {|k,v| "#{k}=#{v}" }
         | 
| 13 | 
            +
                  parameter_list.empty? ? '' : "?#{parameter_list.join('&')}"
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def uri
         | 
| 17 | 
            +
                  URI.parse(@base_url)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def send_request
         | 
| 21 | 
            +
                  response = if @session.single_user?
         | 
| 22 | 
            +
                    single_user_request
         | 
| 23 | 
            +
                  elsif @session.oauth_user?
         | 
| 24 | 
            +
                    oauth_user_request
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  raise ClientError, response.body.inspect unless response.kind_of?(Net::HTTPSuccess)
         | 
| 28 | 
            +
                  response
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def single_user_request
         | 
| 32 | 
            +
                  http = Net::HTTP.new(uri.host, uri.port)
         | 
| 33 | 
            +
                  http.use_ssl = true
         | 
| 34 | 
            +
                  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         | 
| 35 | 
            +
                  http.get("#{uri.path}#{query_string}", 'Authorization' => "GoogleLogin auth=#{@session.auth_token}")
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def oauth_user_request
         | 
| 39 | 
            +
                  @session.access_token.get("#{uri}#{query_string}")
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              class FilterParameters
         | 
| 3 | 
            +
                def self.define_operators(*methods)
         | 
| 4 | 
            +
                  methods.each do |method|
         | 
| 5 | 
            +
                    class_eval <<-CODE
         | 
| 6 | 
            +
                      def #{method}(field, value)
         | 
| 7 | 
            +
                        @filter_hash.merge!({SymbolOperator.new(field, :#{method}) => value})
         | 
| 8 | 
            +
                      end
         | 
| 9 | 
            +
                    CODE
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                define_operators :eql, :not_eql, :gt, :gte, :lt, :lte, :matches,
         | 
| 14 | 
            +
                  :does_not_match, :contains, :does_not_contain, :substring, :not_substring
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                attr_accessor :parameters
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def initialize
         | 
| 19 | 
            +
                  self.parameters = []
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def filters(&block)
         | 
| 23 | 
            +
                  @filter_hash = {}
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  instance_eval &block
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  self.parameters << @filter_hash
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def to_params
         | 
| 31 | 
            +
                  value = self.parameters.map do |param|
         | 
| 32 | 
            +
                    param.map do |k,v|
         | 
| 33 | 
            +
                      next unless k.is_a?(SymbolOperator)
         | 
| 34 | 
            +
                      "#{URI.encode(k.to_google_analytics, /[=<>]/)}#{CGI::escape(v.to_s)}"
         | 
| 35 | 
            +
                    end.join(';') # Hash AND (no duplicate keys)
         | 
| 36 | 
            +
                  end.join(',') # Array OR
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  value.empty? ? {} : {'filters' => value}
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
    
        data/lib/garb/profile.rb
    ADDED
    
    | @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              class Profile
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                include ProfileReports
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                attr_reader :session, :table_id, :title, :account_name, :account_id, :web_property_id
         | 
| 7 | 
            +
                
         | 
| 8 | 
            +
                class Property
         | 
| 9 | 
            +
                  include HappyMapper
         | 
| 10 | 
            +
                  
         | 
| 11 | 
            +
                  tag 'property'
         | 
| 12 | 
            +
                  namespace 'http://schemas.google.com/analytics/2009'
         | 
| 13 | 
            +
                  
         | 
| 14 | 
            +
                  attribute :name, String
         | 
| 15 | 
            +
                  attribute :value, String
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def instance_name
         | 
| 18 | 
            +
                    Garb.from_google_analytics(name)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                class Entry
         | 
| 23 | 
            +
                  include HappyMapper
         | 
| 24 | 
            +
                  
         | 
| 25 | 
            +
                  tag 'entry'
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  element :title, String
         | 
| 28 | 
            +
                  element :tableId, String, :namespace => 'http://schemas.google.com/analytics/2009'
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  has_many :properties, Property
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def initialize(entry, session)
         | 
| 34 | 
            +
                  @session = session
         | 
| 35 | 
            +
                  @title = entry.title
         | 
| 36 | 
            +
                  @table_id = entry.tableId
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  entry.properties.each do |p|
         | 
| 39 | 
            +
                    instance_variable_set :"@#{p.instance_name}", p.value
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def id
         | 
| 44 | 
            +
                  Garb.from_google_analytics(@table_id)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def self.all(session = Session)
         | 
| 48 | 
            +
                  url = "https://www.google.com/analytics/feeds/accounts/default"
         | 
| 49 | 
            +
                  response = DataRequest.new(session, url).send_request      
         | 
| 50 | 
            +
                  Entry.parse(response.body).map {|entry| new(entry, session)}
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def self.first(id, session = Session)
         | 
| 54 | 
            +
                  all(session).detect {|profile| profile.id == id || profile.web_property_id == id }
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              module ProfileReports
         | 
| 3 | 
            +
                def self.add_report_method(klass)
         | 
| 4 | 
            +
                  # demodulize leaves potential to redefine
         | 
| 5 | 
            +
                  # these methods given different namespaces
         | 
| 6 | 
            +
                  method_name = klass.to_s.demodulize.underscore
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  class_eval <<-CODE
         | 
| 9 | 
            +
                    def #{method_name}(opts = {}, &block)
         | 
| 10 | 
            +
                      #{klass}.results(self, opts, &block)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  CODE
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
    
        data/lib/garb/report.rb
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              class Report
         | 
| 3 | 
            +
                include Resource
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                MONTH = 2592000
         | 
| 6 | 
            +
                URL = "https://www.google.com/analytics/feeds/data"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(profile, opts={})
         | 
| 9 | 
            +
                  @profile = profile
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  @start_date = opts.fetch(:start_date, Time.now - MONTH)
         | 
| 12 | 
            +
                  @end_date = opts.fetch(:end_date, Time.now)
         | 
| 13 | 
            +
                  @limit = opts.fetch(:limit, nil)
         | 
| 14 | 
            +
                  @offset = opts.fetch(:offset, nil)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  metrics opts.fetch(:metrics, [])
         | 
| 17 | 
            +
                  dimensions opts.fetch(:dimensions, [])
         | 
| 18 | 
            +
                  sort opts.fetch(:sort, [])
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def results
         | 
| 22 | 
            +
                  ReportResponse.new(send_request_for_body).results
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            module Garb
         | 
| 2 | 
            +
              class ReportParameter
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                attr_reader :elements
         | 
| 5 | 
            +
                
         | 
| 6 | 
            +
                def initialize(name)
         | 
| 7 | 
            +
                  @name = name
         | 
| 8 | 
            +
                  @elements = []
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
                
         | 
| 11 | 
            +
                def name
         | 
| 12 | 
            +
                  @name.to_s
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                def <<(element)
         | 
| 16 | 
            +
                  (@elements += [element].flatten).compact!
         | 
| 17 | 
            +
                  self
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                def to_params
         | 
| 21 | 
            +
                  value = self.elements.map{|param| Garb.to_google_analytics(param)}.join(',')
         | 
| 22 | 
            +
                  value.empty? ? {} : {self.name => value}
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            module Garb  
         | 
| 2 | 
            +
              class ReportResponse
         | 
| 3 | 
            +
                # include Enumerable
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(response_body)
         | 
| 6 | 
            +
                  @xml = response_body
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
                
         | 
| 9 | 
            +
                def parse      
         | 
| 10 | 
            +
                  entries = Entry.parse(@xml)
         | 
| 11 | 
            +
                  
         | 
| 12 | 
            +
                  @results = entries.collect do |entry|
         | 
| 13 | 
            +
                    hash = {}
         | 
| 14 | 
            +
                    
         | 
| 15 | 
            +
                    entry.metrics.each do |m|
         | 
| 16 | 
            +
                      name = m.name.sub(/^ga\:/,'').underscore
         | 
| 17 | 
            +
                      hash.merge!({name => m.value})
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                    
         | 
| 20 | 
            +
                    entry.dimensions.each do |d|
         | 
| 21 | 
            +
                      name = d.name.sub(/^ga\:/,'').underscore
         | 
| 22 | 
            +
                      hash.merge!({name => d.value})
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                    
         | 
| 25 | 
            +
                    OpenStruct.new(hash)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def results
         | 
| 30 | 
            +
                  @results || parse
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
                
         | 
| 33 | 
            +
                class Metric
         | 
| 34 | 
            +
                  include HappyMapper
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  tag 'metric'
         | 
| 37 | 
            +
                  namespace 'http://schemas.google.com/analytics/2009'
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  attribute :name, String
         | 
| 40 | 
            +
                  attribute :value, String
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              
         | 
| 43 | 
            +
                class Dimension
         | 
| 44 | 
            +
                  include HappyMapper
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  tag 'dimension'
         | 
| 47 | 
            +
                  namespace 'http://schemas.google.com/analytics/2009'
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  attribute :name, String
         | 
| 50 | 
            +
                  attribute :value, String
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              
         | 
| 53 | 
            +
                class Entry
         | 
| 54 | 
            +
                  include HappyMapper
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                  tag 'entry'
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  has_many :metrics, Metric
         | 
| 59 | 
            +
                  has_many :dimensions, Dimension
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         |