fitgem 0.3.6 → 0.4.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.
@@ -1,20 +1,67 @@
1
1
  module Fitgem
2
2
  class Client
3
+ # ==========================================
4
+ # Body Measurements Update Methods
5
+ # ==========================================
3
6
 
4
- # ==========================================
5
- # Body Measurements Update Methods
6
- # ==========================================
7
- def body_measurements_on_date(date)
8
- get("/user/#{@user_id}/body/date/#{format_date(date)}.json")
9
- end
7
+ # Get the body measurements logged on a specified date
8
+ #
9
+ # @param [DateTime, String] date The date to retrieve body
10
+ # measurements for. May be a DateTime object or a String
11
+ # in "yyyy-MM-dd" format.
12
+ # @return [Hash] A hash containing the logged body measurements
13
+ # along with any goals set for the current user.
14
+ def body_measurements_on_date(date)
15
+ get("/user/#{@user_id}/body/date/#{format_date(date)}.json")
16
+ end
10
17
 
11
- # ==========================================
12
- # Body Measurements Update Methods
13
- # ==========================================
18
+ # ==========================================
19
+ # Body Measurements Update Methods
20
+ # ==========================================
14
21
 
15
- def log_weight(weight, date, options={})
16
- post("/user/#{@user_id}/body/weight.json", options.merge(:weight => weight, :date => format_date(date)))
17
- end
22
+ # Log weight to fitbit for the current user
23
+ #
24
+ # @param [Integer, String] weight The weight to log, as either
25
+ # an integer or a string in "X.XX'" format
26
+ # @param [DateTime, String] date The date the weight should be
27
+ # logged, as either a DateTime or a String in "yyyy-MM-dd" format
28
+ # @return [Hash]
29
+ #
30
+ # @deprecated {#log_body_measurements} should be used instead of
31
+ # log_weight
32
+ def log_weight(weight, date, options={})
33
+ post("/user/#{@user_id}/body/weight.json", options.merge(:weight => weight, :date => format_date(date)))
34
+ end
18
35
 
36
+ # Log body measurements to fitbit for the current user
37
+ #
38
+ # At least ONE measurement item is REQUIRED in the call, as well as the
39
+ # date. All measurement values to be logged for the user must be either an
40
+ # Integer, a Decimal value, or a String in "X.XX" format. The
41
+ # measurement units used for the supplied measurements are based on
42
+ # which {Fitgem::ApiUnitSystem} is set in {Fitgem::Client#api_unit_system}.
43
+ #
44
+ # @param [Hash] opts The options including data to log for the user
45
+ # @option opts [Integer, Decimal, String] :weight Weight measurement
46
+ # @option opts [Integer, Decimal, String] :waist Waist measurement
47
+ # @option opts [Integer, Decimal, String] :thigh Thigh measurement
48
+ # @option opts [Integer, Decimal, String] :neck Neck measurement
49
+ # @option opts [Integer, Decimal, String] :hips Hips measurement
50
+ # @option opts [Integer, Decimal, String] :forearm Forearm measurement
51
+ # @option opts [Integer, Decimal, String] :fat Body fat percentage measurement
52
+ # @option opts [Integer, Decimal, String] :chest Chest measurement
53
+ # @option opts [Integer, Decimal, String] :calf Calf measurement
54
+ # @option opts [Integer, Decimal, String] :bicep Bicep measurement
55
+ # @option opts [DateTime, Date, String] :date Date to log measurements
56
+ # for; provided either as a DateTime, Date, or a String in
57
+ # "yyyy-MM-dd" format
58
+ #
59
+ # @return [Hash] Hash containing the key +:body+ with an inner hash
60
+ # of all of the logged measurements
61
+ #
62
+ # @since v0.4.0
63
+ def log_body_measurements(opts)
64
+ post("/user/#{@user_id}/body.json", opts)
65
+ end
19
66
  end
20
- end
67
+ end
@@ -17,55 +17,161 @@ require 'uri'
17
17
 
18
18
  module Fitgem
19
19
  class Client
20
+ API_VERSION = "1"
21
+ EMPTY_BODY = ""
20
22
 
23
+ # Sets or gets the api_version to be used in API calls
24
+ #"
25
+ # @return [String]
21
26
  attr_accessor :api_version
27
+
28
+ # Sets or gets the api unit system to be used in API calls
29
+ #
30
+ # @return [String]
31
+ #
32
+ # @example Set this using the {Fitgem::ApiUnitSystem}
33
+ # client.api_unit_system = Fitgem::ApiUnitSystem.UK
34
+ # @example May also be set in the constructor call
35
+ # client = Fitgem::Client {
36
+ # :consumer_key => my_key,
37
+ # :consumer_secret => my_secret,
38
+ # :token => fitbit_oauth_token,
39
+ # :secret => :fitbit_oauth_secret,
40
+ # :unit_system => Fitgem::ApiUnitSystem.METRIC
41
+ # }
22
42
  attr_accessor :api_unit_system
43
+
44
+ # Sets or gets the user id to be used in API calls
45
+ #
46
+ # @return [String]
23
47
  attr_accessor :user_id
24
48
 
25
- def initialize(options = {})
26
- @consumer_key = options[:consumer_key]
27
- @consumer_secret = options[:consumer_secret]
28
- @token = options[:token]
29
- @secret = options[:secret]
30
- @proxy = options[:proxy]
31
- @user_id = options[:user_id] || "-"
32
- @api_unit_system = Fitgem::ApiUnitSystem.US
33
- @api_version = "1"
49
+ # Creates a client object to communicate with the fitbit API
50
+ #
51
+ # There are two primary ways to create a client: one if the current
52
+ # fitbit user has not authenticated through fitbit.com, and another
53
+ # if they have already authenticated and you have a stored
54
+ # token/secret returned by fitbit after the user authenticated and
55
+ # authorized your application.
56
+ #
57
+ # @param [Hash] opts The constructor options
58
+ # @option opts [String] :consumer_key The consumer key (required for
59
+ # OAuth)
60
+ # @option opts [String] :consumer_secret The consumer secret (required
61
+ # for OAuth)
62
+ # @option opts [String] :token The token generated by fitbit during the OAuth
63
+ # handshake; stored and re-passed to the constructor to create a
64
+ # 'logged-in' client
65
+ # @option opts [String] :secret The secret generated by fitbit during the
66
+ # OAuth handshake; stored and re-passed to the constructor to
67
+ # create a 'logged-in' client
68
+ # @option opts [String] :proxy A proxy URL to use for API calls
69
+ # @option opts [String] :user_id The Fitbit user id of the logged-in
70
+ # user
71
+ # @option opts [Symbol] :unit_system The unit system to use for API
72
+ # calls; use {Fitgem::ApiUnitSystem} to set during initialization.
73
+ # DEFAULT: {Fitgem::ApiUnitSystem.US}
74
+ #
75
+ # @example User has not yet authorized with fitbit
76
+ # client = Fitgem::Client.new { :consumer_key => my_key, :consumer_secret => my_secret }
77
+ #
78
+ # @example User has already authorized with fitbit, and we have a stored token/secret
79
+ # client = Fitgem::Client.new {
80
+ # :consumer_key => my_key,
81
+ # :consumer_secret => my_secret,
82
+ # :token => fitbit_oauth_token,
83
+ # :secret => fitbit_oauth_secret
84
+ # }
85
+ #
86
+ # @return [Client] A Fitgem::Client; may be in a logged-in state or
87
+ # ready-to-login state
88
+ def initialize(opts)
89
+ missing = [:consumer_key, :consumer_secret] - opts.keys
90
+ if missing.size > 0
91
+ raise Fitgem::InvalidArgumentError, "Missing required options: #{missing.join(',')}"
92
+ end
93
+ @consumer_key = opts[:consumer_key]
94
+ @consumer_secret = opts[:consumer_secret]
95
+
96
+
97
+ @token = opts[:token]
98
+ @secret = opts[:secret]
99
+
100
+ @proxy = opts[:proxy] if opts[:proxy]
101
+ @user_id = opts[:user_id] || "-"
102
+
103
+ @api_unit_system = opts[:unit_system] || Fitgem::ApiUnitSystem.US
104
+ @api_version = API_VERSION
34
105
  end
35
106
 
36
- def authorize(token, secret, options = {})
37
- request_token = OAuth::RequestToken.new(
38
- consumer, token, secret
39
- )
40
- @access_token = request_token.get_access_token(options)
107
+ # Finalize authentication and retrieve an oauth access token
108
+ #
109
+ # @param [String] token The OAuth token
110
+ # @param [String] secret The OAuth secret
111
+ # @param [Hash] opts Additional data
112
+ # @option opts [String] :oauth_verifier The verifier token sent by
113
+ # fitbit after user has logged in and authorized the application.
114
+ # Is included in the body of the callback request, if there was
115
+ # one. Otherwise is shown onscreen for the user to copy/paste
116
+ # back into your application. See {https://wiki.fitbit.com/display/API/OAuth-Authentication-API} for more information.
117
+ #
118
+ # @return [OAuth::AccessToken] An oauth access token; this is not
119
+ # needed to make API calls, since it is stored internally. It is
120
+ # returned so that you may make general OAuth calls if need be.
121
+ def authorize(token, secret, opts={})
122
+ request_token = OAuth::RequestToken.new(consumer, token, secret)
123
+ @access_token = request_token.get_access_token(opts)
41
124
  @token = @access_token.token
42
125
  @secret = @access_token.secret
43
126
  @access_token
44
127
  end
45
128
 
129
+ # Reconnect to the fitbit API with a stored oauth token and oauth
130
+ # secret
131
+ #
132
+ # This method should be used if you have previously directed a user
133
+ # through the OAuth process and received a token and secret that
134
+ # were stored for later use. Using +reconnect+ you can
135
+ # 'reconstitute' the access_token required for API calls without
136
+ # needing the user to go through the OAuth process again.
137
+ #
138
+ # @param [String] token The stored OAuth token
139
+ # @param [String] secret The stored OAuth secret
140
+ #
141
+ # @return [OAuth::AccessToken] An oauth access token; this is not
142
+ # needed to make API calls, since it is stored internally. It is
143
+ # returned so that you may make general OAuth calls if need be.
46
144
  def reconnect(token, secret)
47
145
  @token = token
48
146
  @secret = secret
49
147
  access_token
50
148
  end
51
149
 
52
- def request_token(options={})
53
- consumer.get_request_token(options)
150
+ # Get an oauth request token
151
+ #
152
+ # @param [Hash] opts Request token request data; can be used to
153
+ # override default endpoint information for the oauth process
154
+ # @return [OAuth::RequestToken]
155
+ def request_token(opts={})
156
+ consumer.get_request_token(opts)
54
157
  end
55
158
 
56
- def authentication_request_token(options={})
159
+ # Get an authentication request token
160
+ #
161
+ # @param [Hash] opts Additional request token request data
162
+ # @return [OAuth::RequestToken]
163
+ def authentication_request_token(opts={})
57
164
  consumer.options[:authorize_path] = '/oauth/authenticate'
58
- request_token(options)
165
+ request_token(opts)
59
166
  end
60
167
 
61
168
  private
62
169
 
63
170
  def consumer
64
- @consumer ||= OAuth::Consumer.new(
65
- @consumer_key,
66
- @consumer_secret,
67
- { :site => 'http://api.fitbit.com', :request_endpoint => @proxy }
68
- )
171
+ @consumer ||= OAuth::Consumer.new(@consumer_key, @consumer_secret, {
172
+ :site => 'http://api.fitbit.com',
173
+ :proxy => @proxy
174
+ })
69
175
  end
70
176
 
71
177
  def access_token
@@ -73,27 +179,36 @@ module Fitgem
73
179
  end
74
180
 
75
181
  def get(path, headers={})
182
+ extract_response_body raw_get(path, headers)
183
+ end
184
+
185
+ def raw_get(path, headers={})
76
186
  headers.merge!("User-Agent" => "fitgem gem v#{Fitgem::VERSION}", "Accept-Language" => @api_unit_system)
77
187
  uri = "/#{@api_version}#{path}"
78
- oauth_response = access_token.get(uri, headers)
79
- process_response oauth_response
188
+ access_token.get(uri, headers)
80
189
  end
81
190
 
82
191
  def post(path, body='', headers={})
192
+ extract_response_body raw_post(path, body, headers)
193
+ end
194
+
195
+ def raw_post(path, body='', headers={})
83
196
  headers.merge!("User-Agent" => "fitgem gem v#{Fitgem::VERSION}", "Accept-Language" => @api_unit_system)
84
197
  uri = "/#{@api_version}#{path}"
85
- oauth_response = access_token.post(uri, body, headers)
86
- process_response oauth_response
198
+ access_token.post(uri, body, headers)
87
199
  end
88
200
 
89
201
  def delete(path, headers={})
202
+ extract_response_body raw_delete(path, headers)
203
+ end
204
+
205
+ def raw_delete(path, headers={})
90
206
  headers.merge!("User-Agent" => "fitgem gem v#{Fitgem::VERSION}", "Accept-Language" => @api_unit_system)
91
207
  uri = "/#{@api_version}#{path}"
92
- oauth_response = access_token.delete(uri, headers)
93
- process_response oauth_response
208
+ access_token.delete(uri, headers)
94
209
  end
95
210
 
96
- def process_response(resp)
211
+ def extract_response_body(resp)
97
212
  resp.nil? || resp.body.nil? ? {} : JSON.parse(resp.body)
98
213
  end
99
214
  end
@@ -1,17 +1,27 @@
1
1
  module Fitgem
2
2
  class Client
3
-
4
3
  # ==========================================
5
4
  # Device Retrieval Methods
6
5
  # ==========================================
7
-
6
+
7
+ # Get a list of devices for the current account
8
+ #
9
+ # @return [Array] An array of hashes, each of which describes
10
+ # a device attaached to the current account
8
11
  def devices
9
12
  get("/user/#{@user_id}/devices.json")
10
13
  end
11
-
14
+
15
+ # Get the details about a specific device
16
+ #
17
+ # The ID required for this call could be found by getting the list
18
+ # of devices with a call to {#devices}.
19
+ #
20
+ # @param [Integer, String] device_id The ID of the device to get
21
+ # details for
22
+ # @return [Hash] Hash containing device information
12
23
  def device_info(device_id)
13
24
  get("/user/#{@user_id}/devices/#{device_id}.json")
14
25
  end
15
-
16
26
  end
17
- end
27
+ end
@@ -1,10 +1,13 @@
1
1
  module Fitgem
2
2
  class InvalidArgumentError < ArgumentError
3
3
  end
4
-
4
+
5
5
  class UserIdError < Exception
6
6
  end
7
-
8
- class InvalidTimeRange < ArgumentError
7
+
8
+ class InvalidDateArgument < InvalidArgumentError
9
9
  end
10
- end
10
+
11
+ class InvalidTimeRange < InvalidArgumentError
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module Fitgem
2
+ # Enumeration of food form types
3
+ #
4
+ # Primarily used in calls to {#create_food}.
5
+ class FoodFormType
6
+ # Food in liquid form
7
+ # @return [String]
8
+ def self.LIQUID
9
+ "LIQUID"
10
+ end
11
+
12
+ # Food in dry form
13
+ # @return [String]
14
+ def self.DRY
15
+ "DRY"
16
+ end
17
+ end
18
+ end
@@ -1,26 +1,45 @@
1
1
  module Fitgem
2
2
  class Client
3
-
4
3
  # ==========================================
5
4
  # Food Retrieval Methods
6
5
  # ==========================================
7
6
 
7
+ # Get all foods logged on the supplied date
8
+ #
9
+ # @param [DateTime, Date, String] date
10
+ # @return [Array] A list of the foods, each of which is a
11
+ # Hash containing the food details
8
12
  def foods_on_date(date)
9
13
  get("/user/#{@user_id}/foods/log/date/#{format_date(date)}.json")
10
14
  end
11
15
 
16
+ # Get a list of the recently logged foods
17
+ #
18
+ # @return [Array] A list of foods, each of which is a Hash
19
+ # containing the food details
12
20
  def recent_foods()
13
21
  get("/user/#{@user_id}/foods/log/recent.json")
14
22
  end
15
23
 
24
+ # Get a list of the frequently logged foods
25
+ #
26
+ # @return [Array] A list of foods, each of which is a Hash
27
+ # containing the food details
16
28
  def frequent_foods()
17
29
  get("/user/#{@user_id}/foods/log/frequent.json")
18
30
  end
19
31
 
32
+ # Get a list of the favorite logged foods
33
+ #
34
+ # @return [Array] A list of foods, each of which is a Hash
35
+ # containing the food details
20
36
  def favorite_foods()
21
37
  get("/user/#{@user_id}/foods/log/favorite.json")
22
38
  end
23
39
 
40
+ # Get a list of all of the available food units
41
+ #
42
+ # @return [Array] List of food units
24
43
  def foods_units()
25
44
  get("/foods/units.json")
26
45
  end
@@ -30,12 +49,18 @@ module Fitgem
30
49
  # ==========================================
31
50
 
32
51
  # Search the foods database
33
- def find_food(query_string)
34
- get("/foods/search.json?query=#{URI.escape(query_string)}")
52
+ #
53
+ # @param [String] query The query parameters for the food search
54
+ # @return [Array] A list of foods, each of which is a Hash
55
+ # containing detailed food information
56
+ def find_food(query)
57
+ get("/foods/search.json?query=#{URI.escape(query)}")
35
58
  end
36
59
 
37
- # Using the foodId field from the results of find_food(), query
38
- # the details of a specific food
60
+ # Get detailed information for a food
61
+ #
62
+ # @param [Integer, String] food_id
63
+ # @return [Hash] Hash containing detailed food information
39
64
  def food_info(food_id)
40
65
  get("/foods/#{food_id}.json")
41
66
  end
@@ -44,17 +69,38 @@ module Fitgem
44
69
  # Food Update Methods
45
70
  # ==========================================
46
71
 
47
- # Send the following required ID's in the options hash:
48
- # options[:foodId] => ID of the food to log
49
- # options[:mealTypeId] => ID of the meal to log the food for
50
- # options[:unitId] => ID of the unit to log with the food
51
- # (typically retrieved via a previous call to get Foods (all, recent, frequent, favorite) or Food Units. )
52
- # options[:amount] => Amount consumed of the selected unit; a floating point number
53
- # options[:date] => Log date in the format yyyy-MM-dd
54
- def log_food(options)
55
- post("/user/#{@user_id}/foods/log.json", options)
72
+ # Log food to fitbit for the current user
73
+ #
74
+ # To log a food, either a +foodId+ or +foodName+ is REQUIRED.
75
+ #
76
+ # @param [Hash] opts Food log options
77
+ # @option opts [Integer, String] :foodId Food id
78
+ # @option opts [String] :foodName Food entry name
79
+ # @option opts [String] :brandName Brand name, valid only with foodName
80
+ # @option opts [Integer, String] :calories Calories for this serving size,
81
+ # valid only with foodName
82
+ # @option opts [Integer, String] :mealTypeId Meal type id; (1 -
83
+ # Breakfast, 2 - Morning Snack, 3 - Lunch, 4 - Afternoon Snack, 5 - Dinner, 7 - Anytime)
84
+ # @option opts [Integer, String] :unitId Unit id; typically
85
+ # retrieved via previous calls to {#foods_on_date},
86
+ # {#recent_foods}, {#favorite_foods}, {#frequent_foods},
87
+ # {#find_food}, or {#foods_units}
88
+ # @option opts [Integer, Decimal, String] :amount Amount consumed;
89
+ # if a String, must be in the format "X.XX"
90
+ # @option opts [DateTime, Date, String] :date Log entry date; in the
91
+ # format "yyyy-MM-dd" if a String
92
+ # @option opts [Boolean] :favorite Add food to favorites after
93
+ # creating the log
94
+ #
95
+ # @return [Hash] Hash containing confirmation of the logged food
96
+ def log_food(opts)
97
+ post("/user/#{@user_id}/foods/log.json", opts)
56
98
  end
57
99
 
100
+ # Mark a food as a favorite
101
+ #
102
+ # @param [Integer, String] food_id Food id
103
+ # @return [Hash] Empty denotes success
58
104
  def add_favorite_food(food_id)
59
105
  post("/user/#{@user_id}/foods/log/favorite/#{food_id}.json")
60
106
  end
@@ -63,10 +109,19 @@ module Fitgem
63
109
  # Food Removal Methods
64
110
  # ==========================================
65
111
 
112
+ # Remove a logged food entry
113
+ #
114
+ # @param [Integer, String] food_log_id The ID of the food log, which
115
+ # is the ID returned in the response when {#log_food} is called.
116
+ # @return [Hash] Empty hash denotes success
66
117
  def delete_logged_food(food_log_id)
67
118
  delete("/user/#{@user_id}/foods/log/#{food_log_id}.json")
68
119
  end
69
120
 
121
+ # Unmark a food as a favorite
122
+ #
123
+ # @param [Integer, String] food_id Food id
124
+ # @return [Hash] Empty hash denotes success
70
125
  def remove_favorite_food(food_id)
71
126
  delete("/user/#{@user_id}/foods/favorite/#{food_id}.json")
72
127
  end
@@ -75,15 +130,26 @@ module Fitgem
75
130
  # Food Creation Methods
76
131
  # ==========================================
77
132
 
78
- # Create a food defined by the following items in the options hash:
79
- # options[:name] (required) => Food name
80
- # options[:defaultFoodMeasurementUnitId] (required) => Unit id; default measurement unit; full list of units could be retrieved via a previous calls to Get Food Units
81
- # options[:defaultServingSize] (required) => Size of the default serving; nutritional values should be provided for this serving size
82
- # options[:calories] (required) => Calories in the default serving size
83
- # options[:formType] (optional) => Form type; (LIQUID or DRY)
84
- # options[:description] (optional) => Description
85
- def create_food(options)
86
- post("/foods.json", options)
133
+ # Create a new food and add it to fitbit
134
+ #
135
+ # @param [Hash] opts The data used to create the food
136
+ # @option opts [String] :name Food name
137
+ # @option opts [Integer, String] :defaultFoodMeasurementUnitId Unit id; typically
138
+ # retrieved via previous calls to {#foods_on_date},
139
+ # {#recent_foods}, {#favorite_foods}, {#frequent_foods},
140
+ # {#find_food}, or {#foods_units}
141
+ # @option opts [Integer, String] :defaultServingSize The default
142
+ # serving size of the food
143
+ # @option opts [Integer, String] :calories The number of calories in
144
+ # the default serving size
145
+ # @option opts [String] :formType Form type; LIQUID or DRY - use
146
+ # {Fitgem::FoodFormType#LIQUID} or {Fitgem::FoodFormType#DRY}
147
+ # @option opts [String] :description Food description
148
+ #
149
+ # @return [Hash] If successful, returns a hash containing
150
+ # confirmation of the food that was added
151
+ def create_food(opts)
152
+ post("/foods.json", opts)
87
153
  end
88
154
  end
89
155
  end