nike_v2_neura 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
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