health_hero-human_api 0.2.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e96aae7b7516f6ea34360d49f1ba1410248d4307
4
+ data.tar.gz: 6018041870317fde160e9a04fa3cfaa84f334ee3
5
+ SHA512:
6
+ metadata.gz: f095c7f3a356d39c995e78d39d71b35c834cdeac9d90ad30dfa71e0ba5bdda41a21fd3070d713378655c01cf9ab22b21f7e75577c3343822d022dfca3551ac6e
7
+ data.tar.gz: c72328901f30426a1de5343d6ddb4ef3ce7205a7c1eb44017268b05034c3bdc91e32056ea576ca2b14814c432903017caa15c1cbaf478560d3aa969546e2fd85
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .env
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 2.1
5
+ - 2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in humanapi.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Justin Aiken
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ [![Build Status](https://travis-ci.org/HealthHero/humanapi.svg?branch=master)][travis][![Gem Version](https://badge.fury.io/rb/health_hero-human_api.png)](http://badge.fury.io/rb/health_hero-human_api)
2
+
3
+ # HumanApi
4
+
5
+ This is Health Hero's fork of the HumanApi gem. The original gem (`human_api`) is found at [https://github.com/humanapi/humanapi](https://github.com/humanapi/humanapi).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'health_hero-human_api'
13
+ ```
14
+
15
+ #### Requirements
16
+
17
+ - Ruby 2.0+
18
+ - Activesupport
19
+ - Activerecord if hooking into User model
20
+
21
+ ## Configuration
22
+
23
+ Let's say you have an User model as follows:
24
+
25
+ ```ruby
26
+ class User < ActiveRecord::Base
27
+ ...
28
+
29
+ humanizable :get_the_token
30
+
31
+ def get_the_token
32
+ # the code to reach the token - attribute, association, Redis, etc.
33
+ end
34
+ end
35
+ ```
36
+
37
+ This configuration is really simple. I suggest it over the second one.
38
+
39
+ Always remember to configure the initializer with the access keys:
40
+ ```ruby
41
+ HumanApi.config do |c|
42
+ c.app_id = ENV['HUMANAPI_KEY']
43
+ c.query_key = ENV['HUMANAPI_SECRET']
44
+ end
45
+ ```
46
+
47
+ ### The alternative
48
+
49
+ If you don't like that configuration, you can use a different one, writing right into the initializer:
50
+
51
+ ```ruby
52
+ HumanApi.config do |c|
53
+ c.app_id = "<YOUR_APP_ID>"
54
+ c.query_key = "<YOUR_QUERY_KEY>"
55
+
56
+ # This is the part where the magics happen
57
+ c.human_model = User # Tell me what is the model you want to use
58
+ c.token_method_name = :human_token # Tell me the method you use to retrieve the token (Inside the human_model)
59
+ end
60
+ ```
61
+
62
+ It should work in both ways, the choice is yours.
63
+
64
+ ## Usage
65
+ Once you did the configuration, the usage of the gem is quite easy:
66
+
67
+ ```ruby
68
+ u = User.first
69
+ u.human.profile # => Will return the humanapi user's profile
70
+ u.human.query(:activities) # => Will return everything you asked for (as an array of hashes)
71
+ ```
72
+
73
+ Just use the _human_ instance method from your User instance and that's it
74
+
75
+ ### The query method
76
+ The query method is meant to ask whatever you want whenever you want. Here are some permitted methods (according to humanapi) you can use to retrieve user's data:
77
+
78
+ ```ruby
79
+ :profile
80
+ :activities
81
+ :blood_glucose
82
+ :blood_pressure
83
+ :body_fat
84
+ :genetic_traits
85
+ :heart_rate
86
+ :height
87
+ :locations
88
+ :sleeps
89
+ :weight
90
+ :bmi
91
+ ```
92
+
93
+ ### Query Options
94
+ Mixing up these methods with some options will give you what you want:
95
+
96
+ ```ruby
97
+ u.human.query(:activities, summary: true) #=> will give you a summary of the activities
98
+ u.human.query(:sleeps, date: "2014-01-01") #=> Will give you a single sleep measurement
99
+
100
+ # Getting KPIs (KPIs are just single values you get to retrieve a measurements average value)
101
+ u.human.query(:weight) #=> Will give you a single weight value (The avg I guess)
102
+
103
+ # Getting readings (If you begin with a single avg value and you wanna go deeper)
104
+ u.human.query(:weight, readings: true)
105
+
106
+ # Return metadata (not just the array of hashes)
107
+ u.human.query(:activities, return_metadata: true) #=> Nestful::Response object, with headers and body available
108
+
109
+ # Manual offset/limit:
110
+ u.human.query(:activities, limit: 3, offset: 50).count #=> 3
111
+
112
+ # Return all of a user's data jammed together, despite being across multiple pages:
113
+ u.human.query(:activities).count #=> 50
114
+ u.human.query(:activities, fetch_all: true).count #=> 321
115
+ ```
116
+
117
+ Lastly, as a common rule, I've identified a pattern in humanapis.
118
+ - If the method name is plural, it will give you multiple measurements when calling it. In addition, you can ask for a summary: true, for a group of value in a specific date: "DATE" or for a single known measurement id: "measurement_id"
119
+ - If the method name is singular, it will give you a single avg value for what you asked. In addition, you can ask for all readings: true and for all readings => true in a specific date: "DATE".
120
+
121
+ ## Common errors and troubleshooting
122
+
123
+ ### 'rewrite_human_model': Could not find 'token' in User
124
+ - Causes: it does mean that the method you suggested does not exist!
125
+ - What to check: Check if you misspelled the method name or the attribute does not exist.
126
+ - Solving: If this does not solve, try using the humanizable function passing a method you can create in your model to retrieve manually just the token.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new :spec
5
+ task default: :spec
data/human_api.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'human_api/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "health_hero-human_api"
8
+ spec.version = HumanApi::VERSION
9
+ spec.authors = ["Justin Aiken"]
10
+ spec.email = ["justin@gohealthhero.com"]
11
+ spec.description = %q{API client for HumanAPI}
12
+ spec.summary = %q{API client for HumanAPI}
13
+ spec.homepage = "https://github.com/HealthHero/humanapi"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(spec)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "nestful", "~> 1.0.7"
23
+ spec.add_dependency "json", "~> 1.8.1"
24
+ spec.add_dependency "activesupport", "> 3.0"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "rspec", "~> 3.1"
29
+ spec.add_development_dependency "vcr"
30
+ spec.add_development_dependency "webmock"
31
+ spec.add_development_dependency "dotenv"
32
+ spec.add_development_dependency "pry"
33
+ end
@@ -0,0 +1,16 @@
1
+ class String
2
+ def is_singular?
3
+ self.pluralize != self and self.singularize == self
4
+ end
5
+ end
6
+
7
+
8
+ class ActiveRecord::Base
9
+ attr_accessor :human_var, :test
10
+
11
+ def self.humanizable(method)
12
+ define_method :human do
13
+ @human_var ||= HumanApi::Human.new(access_token: self.send(method.to_sym))
14
+ end
15
+ end
16
+ end if defined? ActiveRecord
@@ -0,0 +1,40 @@
1
+ module HumanApi
2
+ class App < Nestful::Resource
3
+
4
+ endpoint 'https://api.humanapi.co'
5
+ path "/v1/apps/#{HumanApi.config.app_id}"
6
+ options auth_type: :basic, user: HumanApi.config.query_key, password: ''
7
+
8
+ # Get the humans of your app:
9
+ def self.humans
10
+ get 'users'
11
+ rescue Nestful::UnauthorizedAccess => e
12
+ if HumanApi.config.handle_access_error
13
+ HumanApi.config.handle_access_error.call e
14
+ else
15
+ raise if HumanApi.config.raise_access_errors
16
+ []
17
+ end
18
+ end
19
+
20
+ # Create a new human:
21
+ def self.create_human(id)
22
+ response = post 'users', externalId: id
23
+
24
+ # If the response is true
25
+ if response.status >= 200 && response.status < 300 # Leave it for now
26
+ JSON.parse response.body
27
+ else
28
+ # Else tell me something went wrong:
29
+ false # Nothing was created
30
+ end
31
+ rescue Nestful::UnauthorizedAccess => e
32
+ if HumanApi.config.handle_access_error
33
+ HumanApi.config.handle_access_error.call e
34
+ else
35
+ raise if HumanApi.config.raise_access_errors
36
+ false
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ module HumanApi
2
+ class Config
3
+ attr_accessor :app_id, :query_key, :human_model, :token_method_name, :hardcore, :raise_access_errors, :handle_access_error
4
+
5
+ CHECK_THE_GUIDE = "Read the guide for more information. (https://github.com/Pazienti/humanapi)"
6
+
7
+ def initialize
8
+ @hardcore = false
9
+ @raise_access_errors = false
10
+ end
11
+
12
+ def configure
13
+ rewrite_human_model
14
+ end
15
+
16
+ def rewrite_human_model
17
+ if human_model.present? && token_method_name.present?
18
+ if human_model.instance_methods.include?(token_method_name)
19
+
20
+ human_model.class_eval do
21
+ attr_accessor :human_var
22
+
23
+ def human
24
+ @human_var ||= HumanApi::Human.new(access_token: self.send(HumanApi.config.token_method_name.to_sym))
25
+ end
26
+ end
27
+ else
28
+ raise "Could not find '#{token_method_name}' in #{human_model}. #{CHECK_THE_GUIDE}"
29
+ end
30
+ else
31
+ # unless hardcore
32
+ # raise "You must set a human_model and a token_method_name. #{CHECK_THE_GUIDE}"
33
+ # end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,106 @@
1
+ module HumanApi
2
+ class Human < Nestful::Resource
3
+
4
+ attr_reader :token
5
+
6
+ endpoint 'https://api.humanapi.co'
7
+ path '/v1/human'
8
+
9
+ # The available methods for this api
10
+ AVAILABLE_METHODS = %i{
11
+ profile activities blood_glucose blood_oxygen blood_pressure body_fat genetic_traits heart_rate height locations sleeps weight bmi
12
+ }.freeze
13
+
14
+ def initialize(options)
15
+ @token = options[:access_token]
16
+ super
17
+ end
18
+
19
+ # Profile =====================================
20
+
21
+ def summary
22
+ get '', access_token: token
23
+ rescue Nestful::UnauthorizedAccess => e
24
+ if HumanApi.config.handle_access_error
25
+ HumanApi.config.handle_access_error.call e
26
+ else
27
+ raise if HumanApi.config.raise_access_errors
28
+ false
29
+ end
30
+ end
31
+
32
+ def profile(options = {})
33
+ query :profile, options
34
+ end
35
+
36
+ # =============================================
37
+
38
+ def query(method, options = {})
39
+ unless AVAILABLE_METHODS.include? method.to_sym
40
+ raise ArgumentError, "The method '#{method}' does not exist!"
41
+ end
42
+
43
+ # From sym to string
44
+ method = method.to_s
45
+
46
+ # The base of the url
47
+ url = "#{method}"
48
+
49
+ # If it is a singular word prepare for readings
50
+ if method.is_singular?
51
+ if options[:readings] == true
52
+ url += "/readings"
53
+ end
54
+ else
55
+ if options[:summary] == true
56
+ url += "/summary"
57
+ end
58
+ end
59
+
60
+ # You passed a date
61
+ if options[:date].present?
62
+ # Make a request for a specific date
63
+ url += "/daily/#{options[:date]}"
64
+ # If you passed an id
65
+ elsif options[:id].present?
66
+ # Make a request for a single
67
+ url += "/#{options[:id]}"
68
+ end
69
+
70
+ params = {access_token: token}
71
+
72
+ if options[:fetch_all]
73
+ results = []
74
+ first_request = get url, params
75
+ total_size = first_request.headers['x-total-count'].to_i
76
+ results = results + JSON.parse(first_request.body)
77
+ next_page_link = first_request.headers['link'].match(/<(https[^>]*)>/)[1]
78
+
79
+ while results.count < total_size
80
+ next_page = Nestful.get next_page_link
81
+ next_page_link = next_page.headers['link'].match(/<(https[^>]*)>/)[1]
82
+ results = results + JSON.parse(next_page.body)
83
+ end
84
+
85
+ results
86
+ else
87
+ params.merge!(limit: options[:limit]) if options[:limit].present?
88
+ params.merge!(offset: options[:offset]) if options[:offset].present?
89
+
90
+ result = get url, params
91
+ if options[:return_metadata]
92
+ result
93
+ else
94
+ JSON.parse result.body
95
+ end
96
+ end
97
+ end
98
+ rescue Nestful::UnauthorizedAccess => e
99
+ if HumanApi.config.handle_access_error
100
+ HumanApi.config.handle_access_error.call e
101
+ else
102
+ raise if HumanApi.config.raise_access_errors
103
+ false
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ module HumanApi
2
+ VERSION = "0.2.0"
3
+ end
data/lib/human_api.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'active_support/all'
2
+ require 'json'
3
+ require 'nestful'
4
+ require 'human_api/version'
5
+ require 'human_api/config'
6
+ require 'config/initializers/core_ext'
7
+
8
+ module HumanApi
9
+
10
+ @config = HumanApi::Config.new
11
+
12
+ def self.config
13
+ if block_given?
14
+ yield @config
15
+ @config.configure
16
+ else
17
+ @config
18
+ end
19
+ end
20
+
21
+ autoload :Human, 'human_api/human'
22
+ autoload :App, 'human_api/app'
23
+ end
@@ -0,0 +1,40 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://APP_ID:@api.humanapi.co/v1/apps/CLIENT_ID/users
6
+ body:
7
+ encoding: US-ASCII
8
+ string: externalId=test_user
9
+ headers:
10
+ Content-Type:
11
+ - application/x-www-form-urlencoded
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - "*/*"
16
+ User-Agent:
17
+ - Ruby
18
+ response:
19
+ status:
20
+ code: 201
21
+ message: Created
22
+ headers:
23
+ Content-Type:
24
+ - application/json; charset=utf-8
25
+ Date:
26
+ - Tue, 01 Sep 2015 18:23:56 GMT
27
+ Server:
28
+ - nginx/1.4.6 (Ubuntu)
29
+ X-Powered-By:
30
+ - Express
31
+ Content-Length:
32
+ - '200'
33
+ Connection:
34
+ - keep-alive
35
+ body:
36
+ encoding: UTF-8
37
+ string: '{"humanId":"abcd","externalId":"test_user","appId":"CLIENT_ID","createdAt":"2015-09-01T18:18:18.500Z","updatedAt":"2015-09-01T18:19:55.177Z"}'
38
+ http_version:
39
+ recorded_at: Tue, 01 Sep 2015 18:19:55 GMT
40
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,54 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://api.humanapi.co/v1/human/activities?access_token=token
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Access-Control-Allow-Headers:
22
+ - Content-Type, Authorization, Accept
23
+ Access-Control-Allow-Methods:
24
+ - OPTIONS,GET,HEAD
25
+ Access-Control-Allow-Origin:
26
+ - "*"
27
+ Access-Control-Max-Age:
28
+ - '86400'
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Date:
32
+ - Tue, 01 Sep 2015 19:07:34 GMT
33
+ Etag:
34
+ - '"-1281148962"'
35
+ Link:
36
+ - <https://api.humanapi.co/v1/human/activities?access_token=token&offset=50>;
37
+ rel="next",<https://api.humanapi.co/v1/human/activities?access_token=token&offset=200>;
38
+ rel="last"
39
+ Server:
40
+ - nginx/1.4.6 (Ubuntu)
41
+ X-Powered-By:
42
+ - Express
43
+ X-Total-Count:
44
+ - '234'
45
+ Content-Length:
46
+ - '20611'
47
+ Connection:
48
+ - keep-alive
49
+ body:
50
+ encoding: UTF-8
51
+ string: '[{"id":"55e37dd14a99f60b00f9ef96","userId":"55b1327e7313370100c17161","startTime":"2015-08-30T13:40:00.000Z","endTime":"2015-08-30T15:37:56.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":7076,"distance":21167.628734835,"steps":0,"calories":1436,"sourceData":{},"createdAt":"2015-08-30T22:04:01.355Z","updatedAt":"2015-08-30T22:04:01.355Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55e37dd14a99f60b00f9ef9c","userId":"55b1327e7313370100c17161","startTime":"2015-08-29T17:42:00.000Z","endTime":"2015-08-29T18:12:26.000Z","tzOffset":"-06:00","type":"cycling","source":"runkeeper","duration":1826,"distance":10863.072,"steps":0,"calories":307,"sourceData":{},"createdAt":"2015-08-30T22:04:01.366Z","updatedAt":"2015-08-30T22:04:01.366Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55e37dd14a99f60b00f9efa2","userId":"55b1327e7313370100c17161","startTime":"2015-08-28T14:54:48.000Z","endTime":"2015-08-28T15:27:45.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":1977,"distance":6444.04127907749,"steps":0,"calories":434,"sourceData":{},"createdAt":"2015-08-30T22:04:01.376Z","updatedAt":"2015-08-30T22:04:01.376Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55e37dd14a99f60b00f9efa7","userId":"55b1327e7313370100c17161","startTime":"2015-08-27T16:05:38.000Z","endTime":"2015-08-27T16:42:46.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2228,"distance":6845.75340414214,"steps":0,"calories":468,"sourceData":{},"createdAt":"2015-08-30T22:04:01.384Z","updatedAt":"2015-08-30T22:04:01.384Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55e37dd14a99f60b00f9efab","userId":"55b1327e7313370100c17161","startTime":"2015-08-26T15:10:33.000Z","endTime":"2015-08-26T15:45:27.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2094,"distance":6562.9452587098,"steps":0,"calories":442,"sourceData":{},"createdAt":"2015-08-30T22:04:01.391Z","updatedAt":"2015-08-30T22:04:01.391Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55e37dd14a99f60b00f9efaf","userId":"55b1327e7313370100c17161","startTime":"2015-08-25T14:56:39.000Z","endTime":"2015-08-25T15:30:55.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2056,"distance":6384.72515693381,"steps":0,"calories":426,"sourceData":{},"createdAt":"2015-08-30T22:04:01.397Z","updatedAt":"2015-08-30T22:04:01.397Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55da18c74625a4080005294a","userId":"55b1327e7313370100c17161","startTime":"2015-08-23T15:45:00.000Z","endTime":"2015-08-23T17:16:46.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":5506,"distance":16900.7034856655,"steps":0,"calories":1146,"sourceData":{},"createdAt":"2015-08-23T19:02:31.386Z","updatedAt":"2015-08-23T19:02:31.386Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55d89d174625a40800043e0c","userId":"55b1327e7313370100c17161","startTime":"2015-08-22T14:37:18.000Z","endTime":"2015-08-22T15:16:52.138Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2374.138,"distance":6893.33608423727,"steps":0,"calories":481,"sourceData":{},"createdAt":"2015-08-22T16:02:31.379Z","updatedAt":"2015-08-22T16:02:31.379Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55da18c74625a4080005294e","userId":"55b1327e7313370100c17161","startTime":"2015-08-21T16:08:41.000Z","endTime":"2015-08-21T16:45:54.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2233,"distance":6979.46988309577,"steps":0,"calories":478,"sourceData":{},"createdAt":"2015-08-23T19:02:31.410Z","updatedAt":"2015-08-23T19:02:31.410Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55d4fcf78892cc08002e0522","userId":"55b1327e7313370100c17161","startTime":"2015-08-19T08:56:09.000Z","endTime":"2015-08-19T09:31:53.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2144,"distance":6538.2353476637,"steps":0,"calories":442,"sourceData":{},"createdAt":"2015-08-19T22:02:31.534Z","updatedAt":"2015-08-19T22:02:31.534Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55d4fcf78892cc08002e0528","userId":"55b1327e7313370100c17161","startTime":"2015-08-18T08:53:32.000Z","endTime":"2015-08-18T09:27:53.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2061,"distance":6500.99039061797,"steps":0,"calories":453,"sourceData":{},"createdAt":"2015-08-19T22:02:31.543Z","updatedAt":"2015-08-19T22:02:31.543Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55d205798892cc08002bd2a1","userId":"55b1327e7313370100c17161","startTime":"2015-08-16T07:57:52.000Z","endTime":"2015-08-16T09:38:08.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":6016,"distance":18334.0457494036,"steps":0,"calories":1237,"sourceData":{},"createdAt":"2015-08-17T16:02:01.430Z","updatedAt":"2015-08-17T16:02:01.430Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55d1f7698892cc08002bc7c3","userId":"55b1327e7313370100c17161","startTime":"2015-08-15T09:04:35.000Z","endTime":"2015-08-15T09:49:06.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2671,"distance":8101.67207317344,"steps":0,"calories":556,"sourceData":{},"createdAt":"2015-08-17T15:02:01.409Z","updatedAt":"2015-08-17T15:02:01.409Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55ce1f098892cc080028d16f","userId":"55b1327e7313370100c17161","startTime":"2015-08-14T07:13:18.000Z","endTime":"2015-08-14T07:54:50.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2492,"distance":8088.75966609549,"steps":0,"calories":552,"sourceData":{},"createdAt":"2015-08-14T17:02:01.603Z","updatedAt":"2015-08-14T17:02:01.603Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55cccd898892cc080027c65e","userId":"55b1327e7313370100c17161","startTime":"2015-08-12T07:26:22.000Z","endTime":"2015-08-12T08:04:08.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2266,"distance":7267.66455299153,"steps":0,"calories":495,"sourceData":{},"createdAt":"2015-08-13T17:02:01.428Z","updatedAt":"2015-08-13T17:02:01.428Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55cccd898892cc080027c662","userId":"55b1327e7313370100c17161","startTime":"2015-08-11T08:44:34.000Z","endTime":"2015-08-11T09:17:54.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2000,"distance":6913.36663730125,"steps":0,"calories":465,"sourceData":{},"createdAt":"2015-08-13T17:02:01.434Z","updatedAt":"2015-08-13T17:02:01.434Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55c7bfab8892cc080023a730","userId":"55b1327e7313370100c17161","startTime":"2015-08-09T10:19:22.000Z","endTime":"2015-08-09T12:01:54.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":6152,"distance":17856.6732247731,"steps":0,"calories":1204,"sourceData":{},"createdAt":"2015-08-09T21:01:31.453Z","updatedAt":"2015-08-09T21:01:31.453Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55c68a4b8892cc080022b774","userId":"55b1327e7313370100c17161","startTime":"2015-08-08T11:47:02.000Z","endTime":"2015-08-08T12:33:02.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2760,"distance":8115.59730310348,"steps":0,"calories":569,"sourceData":{},"createdAt":"2015-08-08T23:01:31.786Z","updatedAt":"2015-08-08T23:01:31.786Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55c68a4b8892cc080022b778","userId":"55b1327e7313370100c17161","startTime":"2015-08-06T08:30:04.000Z","endTime":"2015-08-06T09:05:16.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2112,"distance":6777.99816345787,"steps":0,"calories":461,"sourceData":{},"createdAt":"2015-08-08T23:01:31.793Z","updatedAt":"2015-08-08T23:01:31.793Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55c68a4b8892cc080022b77c","userId":"55b1327e7313370100c17161","startTime":"2015-08-05T07:20:47.000Z","endTime":"2015-08-05T07:56:11.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2124,"distance":6548.10519801716,"steps":0,"calories":442,"sourceData":{},"createdAt":"2015-08-08T23:01:31.799Z","updatedAt":"2015-08-08T23:01:31.799Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55c68a4b8892cc080022b780","userId":"55b1327e7313370100c17161","startTime":"2015-08-04T08:13:48.000Z","endTime":"2015-08-04T08:55:13.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2485,"distance":8084.56047541964,"steps":0,"calories":552,"sourceData":{},"createdAt":"2015-08-08T23:01:31.806Z","updatedAt":"2015-08-08T23:01:31.806Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55be92f004ad0d0800e5e552","userId":"55b1327e7313370100c17161","startTime":"2015-08-02T09:18:00.000Z","endTime":"2015-08-02T11:38:23.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":8423,"distance":25811.3561216763,"steps":0,"calories":1735,"sourceData":{},"createdAt":"2015-08-02T22:00:16.429Z","updatedAt":"2015-08-02T22:00:16.429Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55be92f004ad0d0800e5e556","userId":"55b1327e7313370100c17161","startTime":"2015-07-31T09:20:00.000Z","endTime":"2015-07-31T09:50:02.000Z","tzOffset":"-06:00","type":"cycling","source":"runkeeper","duration":1802,"distance":10686.04416,"steps":0,"calories":300,"sourceData":{},"createdAt":"2015-08-02T22:00:16.435Z","updatedAt":"2015-08-02T22:00:16.435Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55be92f004ad0d0800e5e55a","userId":"55b1327e7313370100c17161","startTime":"2015-07-30T08:27:34.000Z","endTime":"2015-07-30T08:55:17.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":1663,"distance":4826.52010011822,"steps":0,"calories":322,"sourceData":{},"createdAt":"2015-08-02T22:00:16.441Z","updatedAt":"2015-08-02T22:00:16.441Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b96506ce04850900911e78","userId":"55b1327e7313370100c17161","startTime":"2015-07-29T08:26:11.000Z","endTime":"2015-07-29T09:03:16.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2225,"distance":6537.06814296831,"steps":0,"calories":444,"sourceData":{},"createdAt":"2015-07-29T23:43:02.588Z","updatedAt":"2015-07-29T23:43:02.588Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b96506ce04850900911e7a","userId":"55b1327e7313370100c17161","startTime":"2015-07-28T08:37:50.000Z","endTime":"2015-07-28T09:20:42.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2572,"distance":8190.82102893706,"steps":0,"calories":564,"sourceData":{},"createdAt":"2015-07-29T23:43:02.596Z","updatedAt":"2015-07-29T23:43:02.596Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b96506ce04850900911e7c","userId":"55b1327e7313370100c17161","startTime":"2015-07-26T07:27:06.000Z","endTime":"2015-07-26T09:25:37.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":7111,"distance":21765.9545514157,"steps":0,"calories":1462,"sourceData":{},"createdAt":"2015-07-29T23:43:02.605Z","updatedAt":"2015-07-29T23:43:02.605Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b96506ce04850900911e7e","userId":"55b1327e7313370100c17161","startTime":"2015-07-25T07:57:20.000Z","endTime":"2015-07-25T09:03:38.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":3978,"distance":12897.1377998602,"steps":0,"calories":862,"sourceData":{},"createdAt":"2015-07-29T23:43:02.612Z","updatedAt":"2015-07-29T23:43:02.612Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b26071ce04850900803746","userId":"55b1327e7313370100c17161","startTime":"2015-07-24T09:02:13.000Z","endTime":"2015-07-24T09:35:01.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":1968,"distance":6548.89061920189,"steps":0,"calories":441,"sourceData":{},"createdAt":"2015-07-24T15:57:37.580Z","updatedAt":"2015-07-24T15:57:37.580Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b13a07ce048509007e1659","userId":"55b1327e7313370100c17161","startTime":"2015-07-23T09:07:00.000Z","endTime":"2015-07-23T09:50:35.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2615,"distance":8085.25682848678,"steps":0,"calories":553,"sourceData":{},"createdAt":"2015-07-23T19:01:27.874Z","updatedAt":"2015-07-23T19:01:27.874Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b13a07ce048509007e165c","userId":"55b1327e7313370100c17161","startTime":"2015-07-22T08:33:03.000Z","endTime":"2015-07-22T09:04:34.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":1891,"distance":5516.98266195084,"steps":0,"calories":364,"sourceData":{},"createdAt":"2015-07-23T19:01:27.879Z","updatedAt":"2015-07-23T19:01:27.879Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfde3","userId":"55b1327e7313370100c17161","startTime":"2015-07-19T10:30:24.000Z","endTime":"2015-07-19T11:16:54.000Z","tzOffset":"-06:00","type":"elliptical","source":"runkeeper","duration":2790,"distance":7370.79537038086,"steps":0,"calories":500,"sourceData":{},"createdAt":"2015-07-23T18:36:44.310Z","updatedAt":"2015-07-23T18:36:44.310Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfde6","userId":"55b1327e7313370100c17161","startTime":"2015-07-18T11:02:13.000Z","endTime":"2015-07-18T11:48:13.000Z","tzOffset":"-06:00","type":"cycling","source":"runkeeper","duration":2760,"distance":17541.8489222168,"steps":0,"calories":422,"sourceData":{},"createdAt":"2015-07-23T18:36:44.317Z","updatedAt":"2015-07-23T18:36:44.317Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfde9","userId":"55b1327e7313370100c17161","startTime":"2015-07-17T11:20:03.000Z","endTime":"2015-07-17T12:04:03.000Z","tzOffset":"-06:00","type":"elliptical","source":"runkeeper","duration":2640,"distance":7145.48742607178,"steps":0,"calories":483,"sourceData":{},"createdAt":"2015-07-23T18:36:44.324Z","updatedAt":"2015-07-23T18:36:44.324Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfdec","userId":"55b1327e7313370100c17161","startTime":"2015-07-16T10:21:43.000Z","endTime":"2015-07-16T10:55:59.569Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2056.569,"distance":6519.07434407043,"steps":0,"calories":416,"sourceData":{},"createdAt":"2015-07-23T18:36:44.331Z","updatedAt":"2015-07-23T18:36:44.331Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfdef","userId":"55b1327e7313370100c17161","startTime":"2015-07-15T10:33:02.000Z","endTime":"2015-07-15T11:03:36.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":1834,"distance":5605.00501698855,"steps":0,"calories":367,"sourceData":{},"createdAt":"2015-07-23T18:36:44.339Z","updatedAt":"2015-07-23T18:36:44.339Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfdf2","userId":"55b1327e7313370100c17161","startTime":"2015-07-08T09:18:16.000Z","endTime":"2015-07-08T09:56:22.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2286,"distance":6714.30396235965,"steps":0,"calories":442,"sourceData":{},"createdAt":"2015-07-23T18:36:44.347Z","updatedAt":"2015-07-23T18:36:44.347Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfdf5","userId":"55b1327e7313370100c17161","startTime":"2015-07-07T09:06:01.000Z","endTime":"2015-07-07T09:40:41.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2080,"distance":6488.45114683759,"steps":0,"calories":430,"sourceData":{},"createdAt":"2015-07-23T18:36:44.355Z","updatedAt":"2015-07-23T18:36:44.355Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfdf8","userId":"55b1327e7313370100c17161","startTime":"2015-07-05T08:28:30.000Z","endTime":"2015-07-05T10:09:50.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":6080,"distance":17752.5679464697,"steps":0,"calories":1176,"sourceData":{},"createdAt":"2015-07-23T18:36:44.363Z","updatedAt":"2015-07-23T18:36:44.363Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfdfb","userId":"55b1327e7313370100c17161","startTime":"2015-07-04T07:54:34.000Z","endTime":"2015-07-04T09:20:47.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":5173,"distance":16107.6936988416,"steps":0,"calories":1066,"sourceData":{},"createdAt":"2015-07-23T18:36:44.370Z","updatedAt":"2015-07-23T18:36:44.370Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfdfe","userId":"55b1327e7313370100c17161","startTime":"2015-07-03T08:31:53.000Z","endTime":"2015-07-03T09:17:58.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2765,"distance":8139.58162263241,"steps":0,"calories":542,"sourceData":{},"createdAt":"2015-07-23T18:36:44.378Z","updatedAt":"2015-07-23T18:36:44.378Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe01","userId":"55b1327e7313370100c17161","startTime":"2015-07-02T09:58:28.000Z","endTime":"2015-07-02T10:35:38.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2230,"distance":6863.0258096377,"steps":0,"calories":458,"sourceData":{},"createdAt":"2015-07-23T18:36:44.385Z","updatedAt":"2015-07-23T18:36:44.385Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe04","userId":"55b1327e7313370100c17161","startTime":"2015-07-01T08:14:40.000Z","endTime":"2015-07-01T08:51:17.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2197,"distance":6887.61287682173,"steps":0,"calories":478,"sourceData":{},"createdAt":"2015-07-23T18:36:44.392Z","updatedAt":"2015-07-23T18:36:44.392Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe07","userId":"55b1327e7313370100c17161","startTime":"2015-06-28T07:21:28.000Z","endTime":"2015-06-28T09:00:12.890Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":5924.89,"distance":17813.0159608506,"steps":0,"calories":1186,"sourceData":{},"createdAt":"2015-07-23T18:36:44.400Z","updatedAt":"2015-07-23T18:36:44.400Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe0a","userId":"55b1327e7313370100c17161","startTime":"2015-06-27T09:16:04.000Z","endTime":"2015-06-27T09:59:54.880Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2630.88,"distance":8096.25450805071,"steps":0,"calories":549,"sourceData":{},"createdAt":"2015-07-23T18:36:44.407Z","updatedAt":"2015-07-23T18:36:44.407Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe0d","userId":"55b1327e7313370100c17161","startTime":"2015-06-26T08:12:00.000Z","endTime":"2015-06-26T08:46:49.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2089,"distance":6692.83905664089,"steps":0,"calories":445,"sourceData":{},"createdAt":"2015-07-23T18:36:44.415Z","updatedAt":"2015-07-23T18:36:44.415Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe10","userId":"55b1327e7313370100c17161","startTime":"2015-06-25T10:02:00.000Z","endTime":"2015-06-25T10:30:52.000Z","tzOffset":"-06:00","type":"cycling","source":"runkeeper","duration":1732,"distance":9527.31648,"steps":0,"calories":255,"sourceData":{},"createdAt":"2015-07-23T18:36:44.422Z","updatedAt":"2015-07-23T18:36:44.422Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe13","userId":"55b1327e7313370100c17161","startTime":"2015-06-24T08:29:20.000Z","endTime":"2015-06-24T09:10:36.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":2476,"distance":8111.74199284542,"steps":0,"calories":552,"sourceData":{},"createdAt":"2015-07-23T18:36:44.429Z","updatedAt":"2015-07-23T18:36:44.429Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe16","userId":"55b1327e7313370100c17161","startTime":"2015-06-23T09:46:49.000Z","endTime":"2015-06-23T10:19:32.000Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":1963,"distance":6505.25232645365,"steps":0,"calories":469,"sourceData":{},"createdAt":"2015-07-23T18:36:44.440Z","updatedAt":"2015-07-23T18:36:44.440Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"},{"id":"55b1343cce048509007dfe19","userId":"55b1327e7313370100c17161","startTime":"2015-06-21T08:49:16.000Z","endTime":"2015-06-21T09:55:05.880Z","tzOffset":"-06:00","type":"running","source":"runkeeper","duration":3949.88,"distance":12873.5890010781,"steps":0,"calories":924,"sourceData":{},"createdAt":"2015-07-23T18:36:44.448Z","updatedAt":"2015-07-23T18:36:44.448Z","humanId":"ef30511710618584fb02a34e5b5c1f7f"}]'
52
+ http_version:
53
+ recorded_at: Tue, 01 Sep 2015 19:03:32 GMT
54
+ recorded_with: VCR 2.9.3