nike_v2_neura 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rbenv-version
6
+ .rspec
7
+ .ruby-version
8
+ .rvmrc
9
+ Gemfile.lock
10
+ coverage
11
+ InstalledFiles
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ NikeV2
2
+ ====================
3
+
4
+ ![Build Status](https://travis-ci.org/fuelxc/nike_v2.png)
5
+
6
+ Learn more about the Nike+ API at [https://developer.nike.com/](https://developer.nike.com/).
7
+
8
+ ## Installation
9
+ ``` bash
10
+ gem install nike_v2
11
+ ```
12
+
13
+ ### Comming Soon
14
+ Once Nike releases the OAuth api this gem will include the ability to fetch the access token too
15
+
16
+ ## Examples
17
+
18
+ In order to utilize the Nike+ API in its current state, you'll need to do the following:
19
+ * Sign up for a Nike+ account at [http://nikeplus.nike.com/plus/](http://nikeplus.nike.com/plus/)
20
+ * Log into the developer portal and generate an access token at [https://developer.nike.com/](https://developer.nike.com/)
21
+
22
+ ``` ruby
23
+ require 'nike_v2'
24
+
25
+ # Initialize a person
26
+ person = NikeV2::Person.new(access_token: 'a1b2c3d4')
27
+ # Fetch persons summary
28
+ person.summary
29
+ # Fetch a persons activities
30
+ person.activities
31
+ # Load more data for an activity
32
+ activity = person.activities.first
33
+ activity.fetch_data
34
+ # Fetch GPS Data for an activity
35
+ activity.gps_data
36
+ ```
37
+
38
+ The activities api allows you to directly pass arguements to the Nike+ api. It also allows you to prefetch the metrics for each activity returned
39
+ ``` ruby
40
+ #fetch 99 activities
41
+ person.activities(:count => 99)
42
+ #prefetch the metrics for activities
43
+ person.activities(:build_metrics => true)
44
+ ```
45
+
46
+ We also smart load the metrics for activities now so you don't have to explicity load them
47
+ ``` ruby
48
+ person.activities.total_fuel #fetches the metrics if they aren't already loaded
49
+ => 394
50
+ ```
51
+
52
+ As of version 0.3.0 you can cache calls to the Nike+ V2 api. We use the ApiCache (https://github.com/mloughran/api_cache) gem for this and all options in cache directive are passed to the api_cache config. Setting config.cache false disables the cache
53
+ ``` ruby
54
+ # config/initializers/nike_v2.rb
55
+ NikeV2.configure do |config|
56
+ config.cache = {
57
+ :cache => 3600
58
+ }
59
+ end
60
+ ```
61
+
62
+ Possible options:
63
+ ```
64
+ {
65
+ :cache => 600, # 10 minutes After this time fetch new data
66
+ :valid => 86400, # 1 day Maximum time to use old data
67
+ # :forever is a valid option
68
+ :period => 60, # 1 minute Maximum frequency to call API
69
+ :timeout => 5 # 5 seconds API response timeout
70
+ :fail => # Value returned instead of exception on failure
71
+ }
72
+ ```
73
+ ## Making it Better
74
+
75
+ * Fork the project.
76
+ * Make your feature addition or bug fix in a new topic branch within your repo.
77
+ * Add specs for any new or modified functionality.
78
+ * Commit and push your changes to Github
79
+ * Send a pull request
80
+
81
+ ## Inspiration
82
+ I used Kevin Thompson's NikeApi gem as inspiration. Find it here: https://github.com/kevinthompson/nike_api
83
+
84
+
85
+ ## Copyright
86
+
87
+ Copyright (c) 2013 SmashTank Apps, LLC.
88
+
89
+ This program is free software: you can redistribute it and/or modify
90
+ it under the terms of the GNU General Public License as published by
91
+ the Free Software Foundation, either version 3 of the License, or
92
+ (at your option) any later version.
93
+
94
+ This program is distributed in the hope that it will be useful,
95
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
96
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
97
+ GNU General Public License for more details.
98
+
99
+ You should have received a copy of the GNU General Public License
100
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new(:spec)
3
+ task default: :spec
4
+
5
+ require 'rdoc/task'
6
+ Rake::RDocTask.new do |rdoc|
7
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
8
+
9
+ rdoc.rdoc_dir = 'rdoc'
10
+ rdoc.title = "nike_v2 #{version}"
11
+ rdoc.rdoc_files.include('README*')
12
+ rdoc.rdoc_files.include('lib/**/*.rb')
13
+ end
@@ -0,0 +1,18 @@
1
+ class String
2
+ def underscore
3
+ self.gsub(/::/, '/').
4
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
5
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
6
+ tr("-", "_").
7
+ downcase
8
+ end
9
+ end
10
+
11
+ class Range
12
+ def intersection(other)
13
+ raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
14
+ return nil if (self.max < other.begin or other.max < self.begin)
15
+ [self.begin, other.begin].max..[self.max, other.max].min
16
+ end
17
+ alias_method :&, :intersection
18
+ end
data/lib/nike_v2.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'alchemist'
2
+ require 'forwardable'
3
+ require 'ext/core_ext'
4
+ require 'tzinfo'
5
+
6
+ module NikeV2
7
+ def self.configuration
8
+ @configuration ||= Configuration.new
9
+ end
10
+
11
+ def self.configure
12
+ yield(configuration) if block_given?
13
+ end
14
+
15
+ class Configuration
16
+ attr_accessor :cache
17
+
18
+ def initialize
19
+ @cache = false
20
+ end
21
+ end
22
+ end
23
+
24
+ require 'nike_v2/base'
25
+ require 'nike_v2/metric'
26
+ require 'nike_v2/metrics'
27
+ require 'nike_v2/experience_type'
28
+ require 'nike_v2/resource'
29
+ require 'nike_v2/person'
30
+ require 'nike_v2/activity'
31
+ require 'nike_v2/activities'
32
+ require 'nike_v2/summary'
33
+ require 'nike_v2/gps_data'
@@ -0,0 +1,102 @@
1
+ require 'enumerator'
2
+ module NikeV2
3
+ class Activities < Resource
4
+ include Enumerable
5
+ extend Forwardable
6
+
7
+ API_ARGS = [:offset, :count, :start_date, :end_date]
8
+
9
+ def_delegators :@activities_array, :[]=, :<<, :[], :count, :length, :each, :first, :last, :collect
10
+ def_delegator :@person, :access_token
11
+
12
+ API_URL = '/me/sport/activities'
13
+
14
+ Metrics::METRIC_TYPES.each do |type|
15
+ self.class_eval do
16
+
17
+ method_var_name = 'total_' + type.downcase
18
+ instance_variable_set('@' + method_var_name, 0.00)
19
+ define_method(method_var_name){ ivar = instance_variable_get('@' + method_var_name); ivar ||= sum_of(method_var_name)}
20
+
21
+
22
+ define_method("total_#{type.downcase}_during") do |start_date, end_date, convert = false|
23
+ @activities_array.reject{|a| ((a.started_at..a.ended_at) & (start_date..end_date)).nil?}.collect{|a| a.send("total_#{type.downcase}_during", start_date, end_date, convert)}.inject(:+)
24
+ end
25
+ end
26
+ end
27
+
28
+ def initialize(attributes = {})
29
+ raise "#{self.class} requires a person." unless attributes.keys.include?(:person)
30
+ @build_metrics = attributes.delete(:build_metrics) || false
31
+ api_args = extract_api_args(attributes)
32
+ set_attributes(attributes)
33
+ @activities_array = []
34
+
35
+
36
+ #TODO: make it pass blocks
37
+ activities = fetch_data(api_args)
38
+ if !activities.nil? && !activities['data'].nil?
39
+ build_activities(activities.delete('data'))
40
+ super(activities)
41
+ end
42
+ end
43
+
44
+ def fetch_more
45
+ unless self.paging['next'].nil? || self.paging['next'] == ''
46
+ fetch_and_build_activities
47
+ end
48
+ self
49
+ end
50
+
51
+ def fetch_all
52
+ until (self.paging['next'].nil? || self.paging['next'] == '') do
53
+ fetch_and_build_activities
54
+ end
55
+ self
56
+ end
57
+
58
+ def paging
59
+ @paging ||= ''
60
+ end
61
+
62
+ private
63
+
64
+
65
+ def sum_of(method_var_name)
66
+ self.collect(&:"#{method_var_name}").inject(:+) || 0.00
67
+ end
68
+
69
+ def build_activities(data)
70
+ if data
71
+ data.each do |activity|
72
+ self << NikeV2::Activity.new({:person => self}.merge(activity))
73
+ end
74
+ end
75
+ if @build_metrics
76
+ self.collect(&:load_data)
77
+ end
78
+
79
+ end
80
+
81
+ def extract_api_args(args)
82
+ args.inject({}){|h,a| h[camelize_word(a.first)] = a.last if API_ARGS.include?(a.first); h}
83
+ end
84
+
85
+ def camelize_word(word, first_letter_in_uppercase = false)
86
+ if !!first_letter_in_uppercase
87
+ word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
88
+ else
89
+ (word[0].to_s.downcase + camelize_word(word, true)[1..-1]).to_sym
90
+ end
91
+ end
92
+
93
+ def fetch_and_build_activities
94
+ url, query = self.paging['next'].match(/^(.*?)\?(.*)$/)[1,2]
95
+ query = query.split(/&/).inject({}){|h,item| k, v = item.split(/\=/); h[k] = v;h}
96
+ activities = fetch_data(query)
97
+ build_activities(activities.delete('data'))
98
+
99
+ @paging = activities.delete('paging')
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,73 @@
1
+ require 'nike_v2/metrics'
2
+ module NikeV2
3
+ class Activity < Resource
4
+ extend Forwardable
5
+ def_delegator :@person, :access_token
6
+
7
+ Metrics::METRIC_TYPES.each do |type|
8
+ self.class_eval do
9
+ def_delegator :metrics, "total_#{type.downcase}"
10
+ def_delegator :metrics, type.downcase
11
+ def_delegator :metrics, "total_#{type.downcase}_during"
12
+ end
13
+ end
14
+
15
+ API_URL = '/me/sport/activities'
16
+
17
+ def initialize(attributes = {})
18
+ raise "#{self.class} requires s person." unless attributes.keys.include?(:person)
19
+ raise "#{self.class} requires an activityId." unless attributes.keys.include?('activityId')
20
+
21
+ build_metrics(attributes)
22
+ super(attributes)
23
+ end
24
+
25
+ def tz
26
+ TZInfo::Timezone.get(self.activity_time_zone)
27
+ end
28
+
29
+ def gps_data
30
+ @gps_data ||= NikeV2::GpsData.new(:activity => self)
31
+ end
32
+
33
+ def metrics
34
+ load_data unless @metrics.is_a?(NikeV2::Metrics)
35
+ @metrics
36
+ end
37
+
38
+ def load_data
39
+ data = fetch_data
40
+ build_metrics(data)
41
+ set_attributes(data)
42
+
43
+ true
44
+ end
45
+
46
+ def started_at
47
+ @started_at ||= Time.parse(self.start_time.gsub('Z', '-') + '00:00')
48
+ end
49
+
50
+ # some reason activities aren't always complete so we need metrics to figure out how long they are
51
+ def ended_at
52
+ @ended_at ||= self.respond_to?(:end_time) ? Time.parse(self.end_time.gsub('Z', '-') + '00:00') : started_at + (metrics.durations.to.seconds).to_f
53
+ end
54
+
55
+ def to_tz(time)
56
+ if time.respond_to?(:strftime)
57
+ return Time.parse(time.strftime('%Y-%m-%d %H:%M:%S ' + self.tz.to_s))
58
+ else
59
+ return Time.parse(time + ' ' + self.tz.to_s)
60
+ end
61
+ end
62
+
63
+ private
64
+ def api_url
65
+ API_URL + "/#{self.activity_id}"
66
+ end
67
+
68
+ def build_metrics(data)
69
+ metrics = data.delete('metrics') || []
70
+ @metrics = metrics.empty? ? nil : NikeV2::Metrics.new(self, metrics)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,17 @@
1
+ module NikeV2
2
+ class Base
3
+ def initialize(attributes={})
4
+ @created_at = attributes.delete('created_at')
5
+ set_attributes(attributes)
6
+ end
7
+
8
+ private
9
+ def set_attributes(attributes)
10
+ attributes.each do |(attr, val)|
11
+ attr = attr.to_s.underscore
12
+ instance_variable_set("@#{attr}", val)
13
+ instance_eval "def #{attr}() @#{attr} end"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module NikeV2
2
+ class ExperienceType
3
+ TO_S_MAP = {
4
+ 'fuelband' => 'Fuel Band',
5
+ 'npluskinecttrg' => 'Nike+ Kinect',
6
+ 'running' => 'Running'
7
+ }
8
+
9
+ TO_KEY_MAP = TO_S_MAP.invert
10
+
11
+ def initialize(code)
12
+ @code = code
13
+ end
14
+
15
+ def code
16
+ @code
17
+ end
18
+
19
+ def to_s
20
+ TO_S_MAP[code.downcase]
21
+ end
22
+
23
+ def self.code_from_string(string)
24
+ TO_KEY_MAP[string]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ module NikeV2
2
+ class GpsData < Resource
3
+ extend Forwardable
4
+ def_delegator :@activity, :access_token
5
+
6
+ def initialize(attributes = {})
7
+ raise "#{self.class} requires an activity." unless attributes.keys.include?(:activity)
8
+ set_attributes(attributes)
9
+ super(fetch_data)
10
+ end
11
+
12
+ private
13
+ def api_url
14
+ self.activity.send(:api_url) + '/gps'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+ module NikeV2
2
+ class Metric
3
+
4
+ def initialize(activity, data)
5
+ @activity = activity
6
+ @unit = data['intervalUnit']
7
+ @type = data ['metricType']
8
+ @values = data['values'].collect(&:to_f)
9
+ self
10
+ end
11
+
12
+ def type
13
+ @type
14
+ end
15
+
16
+ def values
17
+ @values
18
+ end
19
+
20
+ def total
21
+ @total ||= values.inject(:+)
22
+ end
23
+
24
+ def total_during(start, stop, convert_to_local = false)
25
+ if convert_to_local
26
+ start = @activity.to_tz(start)
27
+ stop = @activity.to_tz(stop)
28
+ end
29
+ during(start, stop).collect(&:to_f).inject(:+) rescue 0
30
+ end
31
+
32
+ def during(start, stop)
33
+ start_point = time_to_index(start)
34
+ duration = time_to_index(stop) - start_point
35
+ @values[start_point, duration]
36
+ end
37
+
38
+ def duration
39
+ @values.length.send(@unit.downcase)
40
+ end
41
+
42
+ private
43
+ def time_to_index(time)
44
+ difference = time.to_i - @activity.started_at.to_i
45
+ difference.s.to.send(@unit.downcase).to_s.to_i
46
+ end
47
+ end
48
+ end