fitgem 0.3.6 → 0.4.0

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