Skip to content

Commit

Permalink
New helper methods on Fitgem::Client + small fixes
Browse files Browse the repository at this point in the history
  * Added label_for_measurements method to get the correct labels
    for each type of measurement with respect to the current user's
    settings on fitbit.com
  * Added connected? method to Fitgem::Client for an easy way to find
    out if the client has a valid access_token
  * Added several new error types used in helper methods
  * Bumped version to 0.5.2
  * Fixed an issue in log_body_measurements where the date wasn't being
    run through the format_date helper
  * Added new specs for label_for_measurements method
  • Loading branch information
Zachery Moneypenny committed Mar 4, 2012
1 parent 585748d commit b78329b
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 6 deletions.
34 changes: 32 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,42 @@

# fitgem changelog

## v0.4.0
## v0.5.2

#### 2012-03-04 Zachery Moneypenny <fitgem@whazzmaster.com>

* Added new <tt>symbolize_keys</tt> helper method for turning the string-key based return hashes into symbol-key based ones
* Added new <tt>label_for_measurement</tt> helper method to get the correct unit measurement label given a measurement type and the current user's ApiUnitSystem setting
* Added specs
* Added new <tt>connected?</tt> method on Fitgem::Client that will report whether API calls may be made
* Added <tt>InvalidUnitSystem</tt> error and <tt>InvalidMeasurementType</tt> error
* Fixed a small issue where date values were not being formatted correctly in calls to <tt>log_body_measurements</tt>

## v0.5.1

#### 2012-01-24 Zachery Moneypenny <fitgem@whazzmaster.com>

* Fix for creating and removing data subscriptions
* Updated specs

## v0.5.0

#### 2012-01-22 Zachery Moneypenny <fitgem@whazzmaster.com>

* Added view/log/delete access for blood pressure data
* Added view/log/delete access for glucose data
* Added view/log/delete access for heart rate data
* Added updated time series documentation for new endpoints
* Updated temporal information in the readme
* Added unit tests for <tt>format_time</tt> method
* Updated copyright date

## v0.4.0

#### 2011-11-29 Zachery Moneypenny <fitgem@whazzmaster.com>

* Added YARD documentation to thoroughly document code
* DEPRECATED: <tt>Fitgem::Client#log_weight</tt> method, use <tt>Fitgem::Client#log_body_measurements</tt> instead.
* DEPRECATED: <tt>Fitgem::Client#log_weight</tt> method, use <tt>Fitgem::Client#log_body_measurements</tt> instead.
The new method allows you to log more than weight (bicep size, body fat %, etc.)
* Added <tt>Fitgem::FoodFormType</tt> to be used in calls to <tt>Fitgem::Client#create_food</tt>
* Added <tt>Fitgem::Client#log_sleep</tt> to log sleep data to fitbit
Expand Down
8 changes: 5 additions & 3 deletions lib/fitgem/body_measurements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def body_measurements_on_date(date)
# an integer or a string in "X.XX'" format
# @param [DateTime, String] date The date the weight should be
# logged, as either a DateTime or a String in "yyyy-MM-dd" format
# @return [Hash]
# @return [Hash]
#
# @deprecated {#log_body_measurements} should be used instead of
# log_weight
Expand All @@ -35,7 +35,7 @@ def log_weight(weight, date, options={})

# Log body measurements to fitbit for the current user
#
# At least ONE measurement item is REQUIRED in the call, as well as the
# At least ONE measurement item is REQUIRED in the call, as well as the
# date. All measurement values to be logged for the user must be either an
# Integer, a Decimal value, or a String in "X.XX" format. The
# measurement units used for the supplied measurements are based on
Expand All @@ -54,13 +54,15 @@ def log_weight(weight, date, options={})
# @option opts [Integer, Decimal, String] :bicep Bicep measurement
# @option opts [DateTime, Date, String] :date Date to log measurements
# for; provided either as a DateTime, Date, or a String in
# "yyyy-MM-dd" format
# "yyyy-MM-dd" format
#
# @return [Hash] Hash containing the key +:body+ with an inner hash
# of all of the logged measurements
#
# @since v0.4.0
def log_body_measurements(opts)
# Update the date (if exists)
opts[:date] = format_date(opts[:date]) if opts[:date]
post("/user/#{@user_id}/body.json", opts)
end
end
Expand Down
7 changes: 7 additions & 0 deletions lib/fitgem/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ def reconnect(token, secret)
access_token
end

# Get the current state of the client
#
# @return True if api calls may be made, false if not
def connected?
!@access_token.nil?
end

# Get an oauth request token
#
# @param [Hash] opts Request token request data; can be used to
Expand Down
9 changes: 9 additions & 0 deletions lib/fitgem/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,13 @@ class InvalidTimeArgument < InvalidArgumentError

class InvalidTimeRange < InvalidArgumentError
end

class InvalidUnitSystem < InvalidArgumentError
end

class InvalidMeasurementType < InvalidArgumentError
end

class ConnectionRequiredError < Exception
end
end
117 changes: 117 additions & 0 deletions lib/fitgem/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,122 @@ def format_time(time)
raise Fitgem::InvalidTimeArgument, "Date used must be a valid time object or a string in the format HH:mm; supplied argument is a #{time.class}"
end
end

# Fetch the correct label for the desired measurement unit.
#
# The general use case for this method is that you are using the client for
# a specific user, and wish to get the correct labels for the unit measurements
# returned for that user.
#
# A secondary use case is that you wish to get the label for a measurement given a unit
# system that you supply (by setting the Fitgem::Client.api_unit_system attribute).
#
# In order for this method to get the correct value for the current user's preferences,
# the client must have the ability to make API calls. If you respect_user_unit_preferences
# is passed as 'true' (or left as the default value) and the client cannot make API calls
# then an error will be raised by the method.
#
# @param [Symbol] measurement_type The measurement type to fetch the label for
# @param [Boolean] respect_user_unit_preferences Should the method fetch the current user's
# specific measurement preferences and use those (true), or use the value set on Fitgem::Client.api_unit_system (false)
# @raise [Fitgem::ConnectionRequiredError] Raised when respect_user_unit_preferences is true but the
# client is not capable of making API calls.
# @raise [Fitgem::InvalidUnitSystem] Raised when the current value of Fitgem::Client.api_unit_system
# is not one of [ApiUnitSystem.US, ApiUnitSystem.UK, ApiUnitSystem.METRIC]
# @raise [Fitgem::InvalidMeasurementType] Raised when the supplied measurement_type is not one of
# [:duration, :distance, :elevation, :height, :weight, :measurements, :liquids, :blood_glucose]
# @return [String] The string label corresponding to the measurement type and
# current api_unit_system.
def label_for_measurement(measurement_type, respect_user_unit_preferences=true)
unless [:duration, :distance, :elevation, :height, :weight, :measurements, :liquids, :blood_glucose].include?(measurement_type)
raise InvalidMeasurementType, "Supplied measurement_type parameter must be one of [:duration, :distance, :elevation, :height, :weight, :measurements, :liquids, :blood_glucose], current value is :#{measurement_type}"
end

selected_unit_system = api_unit_system

if respect_user_unit_preferences
unless connected?
raise ConnectionRequiredError, "No connection to Fitbit API; one is required when passing respect_user_unit_preferences=true"
end
# Cache the unit systems for the current user
@unit_systems ||= self.user_info['user'].select {|key, value| key =~ /Unit$/ }

case measurement_type
when :distance
selected_unit_system = @unit_systems["distanceUnit"]
when :height
selected_unit_system = @unit_systems["heightUnit"]
when :liquids
selected_unit_system = @unit_systems["waterUnit"]
when :weight
selected_unit_system = @unit_systems["weightUnit"]
when :blood_glucose
selected_unit_system = @unit_systems["glucoseUnit"]
else
selected_unit_system = api_unit_system
end
end

# Fix the METRIC system difference
selected_unit_system = Fitgem::ApiUnitSystem.METRIC if selected_unit_system == "METRIC"

# Ensure the target unit system is one that we know about
unless [ApiUnitSystem.US, ApiUnitSystem.UK, ApiUnitSystem.METRIC].include?(selected_unit_system)
raise InvalidUnitSystem, "The select unit system must be one of [ApiUnitSystem.US, ApiUnitSystem.UK, ApiUnitSystem.METRIC], current value is #{selected_unit_system}"
end

unit_mappings[selected_unit_system][measurement_type]
end

# Recursively turns arrays and hashes into symbol-key based
# structures.
#
# @param [Array, Hash] The structure to symbolize keys for
# @return A new structure with the keys symbolized
def self.symbolize_keys(obj)
case obj
when Array
obj.inject([]){|res, val|
res << case val
when Hash, Array
symbolize_keys(val)
else
val
end
res
}
when Hash
obj.inject({}){|res, (key, val)|
nkey = case key
when String
key.to_sym
else
key
end
nval = case val
when Hash, Array
symbolize_keys(val)
else
val
end
res[nkey] = nval
res
}
else
obj
end
end

protected

# Defined mappings for unit measurements to labels
def unit_mappings
{
ApiUnitSystem.US => { :duration => "milliseconds", :distance => "miles", :elevation => "feet", :height => "inches", :weight => "pounds", :measurements => "inches", :liquids => "fl oz", :blood_glucose => "mg/dL" },
ApiUnitSystem.UK => { :duration => "milliseconds", :distance => "kilometers", :elevation => "meters", :height => "centimeters", :weight => "stone", :measurements => "centimeters", :liquids => "mL", :blood_glucose => "mmol/l" },
ApiUnitSystem.METRIC => { :duration => "milliseconds", :distance => "kilometers", :elevation => "meters", :height => "centimeters", :weight => "kilograms", :measurements => "centimeters", :liquids => "mL", :blood_glucose => "mmol/l" }
}
end

end
end
2 changes: 1 addition & 1 deletion lib/fitgem/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Fitgem
VERSION = "0.5.1"
VERSION = "0.5.2"
end
83 changes: 83 additions & 0 deletions spec/fitgem_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,87 @@
}.to raise_error Fitgem::InvalidTimeArgument
end
end

describe "#label_for_measurement" do
it "accepts the supported Fitgem::ApiUnitSystem values" do
@client.api_unit_system = Fitgem::ApiUnitSystem.US
@client.label_for_measurement :duration, false
@client.api_unit_system = Fitgem::ApiUnitSystem.UK
@client.label_for_measurement :duration, false
@client.api_unit_system = Fitgem::ApiUnitSystem.METRIC
@client.label_for_measurement :duration, false
end

it "raises an InvalidUnitSystem error if the Fitgem::Client.api_unit_system value is invalid" do
expect {
@client.api_unit_system = "something else entirely"
@client.label_for_measurement :duration, false
}.to raise_error Fitgem::InvalidUnitSystem
end

it "accepts the supported values for the measurement_type parameter" do
@client.label_for_measurement :duration, false
@client.label_for_measurement :distance, false
@client.label_for_measurement :elevation, false
@client.label_for_measurement :height, false
@client.label_for_measurement :weight, false
@client.label_for_measurement :measurements, false
@client.label_for_measurement :liquids, false
@client.label_for_measurement :blood_glucose, false
end

it "raises an InvalidMeasurementType error if the measurement_type parameter is invalid" do
expect {
@client.label_for_measurement :homina, false
}.to raise_error Fitgem::InvalidMeasurementType
end

it "returns the correct values when the unit system is Fitgem::ApiUnitSystem.US" do
@client.api_unit_system = Fitgem::ApiUnitSystem.US
@client.label_for_measurement(:duration, false).should == "milliseconds"
@client.label_for_measurement(:distance, false).should == "miles"
@client.label_for_measurement(:elevation, false).should == "feet"
@client.label_for_measurement(:height, false).should == "inches"
@client.label_for_measurement(:weight, false).should == "pounds"
@client.label_for_measurement(:measurements, false).should == "inches"
@client.label_for_measurement(:liquids, false).should == "fl oz"
@client.label_for_measurement(:blood_glucose, false).should == "mg/dL"
end

it "returns the correct values when the unit system is Fitgem::ApiUnitSystem.UK" do
@client.api_unit_system = Fitgem::ApiUnitSystem.UK
@client.label_for_measurement(:duration, false).should == "milliseconds"
@client.label_for_measurement(:distance, false).should == "kilometers"
@client.label_for_measurement(:elevation, false).should == "meters"
@client.label_for_measurement(:height, false).should == "centimeters"
@client.label_for_measurement(:weight, false).should == "stone"
@client.label_for_measurement(:measurements, false).should == "centimeters"
@client.label_for_measurement(:liquids, false).should == "mL"
@client.label_for_measurement(:blood_glucose, false).should == "mmol/l"
end

it "returns the correct values when the unit system is Fitgem::ApiUnitSystem.METRIC" do
@client.api_unit_system = Fitgem::ApiUnitSystem.METRIC
@client.label_for_measurement(:duration, false).should == "milliseconds"
@client.label_for_measurement(:distance, false).should == "kilometers"
@client.label_for_measurement(:elevation, false).should == "meters"
@client.label_for_measurement(:height, false).should == "centimeters"
@client.label_for_measurement(:weight, false).should == "kilograms"
@client.label_for_measurement(:measurements, false).should == "centimeters"
@client.label_for_measurement(:liquids, false).should == "mL"
@client.label_for_measurement(:blood_glucose, false).should == "mmol/l"
end

context "when respecting the user's unit measurement preferences" do
before(:each) do
@client.stub(:connected?).and_return(true)
@client.stub(:user_info).and_return({"user" => {"distanceUnit"=>"en_GB", "glucoseUnit"=>"en_GB", "heightUnit"=>"en_GB", "waterUnit"=>"METRIC", "weightUnit"=>"en_GB"}})
end

it "returns the correct overridden measurement label" do
@client.api_unit_system = Fitgem::ApiUnitSystem.US
@client.label_for_measurement(:distance).should == "kilometers"
end
end
end
end

0 comments on commit b78329b

Please sign in to comment.