medic 0.0.1 → 0.0.2

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +210 -10
  3. data/lib/medic.rb +1 -3
  4. data/lib/medic/anchor.rb +32 -0
  5. data/lib/medic/anchored_object_query.rb +18 -0
  6. data/lib/medic/correlation_query.rb +22 -0
  7. data/lib/medic/finders.rb +122 -0
  8. data/lib/medic/hk_constants.rb +76 -0
  9. data/lib/medic/interval.rb +29 -0
  10. data/lib/medic/medic.rb +68 -1
  11. data/lib/medic/observer_query.rb +15 -0
  12. data/lib/medic/predicate.rb +39 -0
  13. data/lib/medic/query_options.rb +23 -0
  14. data/lib/medic/sample_query.rb +22 -0
  15. data/lib/medic/sort.rb +24 -0
  16. data/lib/medic/source_query.rb +15 -0
  17. data/lib/medic/statistics_collection_query.rb +39 -0
  18. data/lib/medic/statistics_options.rb +31 -0
  19. data/lib/medic/statistics_query.rb +17 -0
  20. data/lib/medic/store.rb +117 -0
  21. data/lib/medic/types.rb +100 -0
  22. data/lib/medic/version.rb +1 -1
  23. data/spec/medic/anchor.rb +16 -0
  24. data/spec/medic/anchored_object_query_spec.rb +12 -0
  25. data/spec/medic/correlation_query_spec.rb +16 -0
  26. data/spec/medic/hk_constants_spec.rb +67 -0
  27. data/spec/medic/interval_spec.rb +16 -0
  28. data/spec/medic/medic_spec.rb +153 -0
  29. data/spec/medic/observer_query_spec.rb +12 -0
  30. data/spec/medic/predicate_spec.rb +27 -0
  31. data/spec/medic/query_options_spec.rb +24 -0
  32. data/spec/medic/sample_query_spec.rb +12 -0
  33. data/spec/medic/sort_spec.rb +26 -0
  34. data/spec/medic/source_query_spec.rb +12 -0
  35. data/spec/medic/statistics_collection_query_spec.rb +27 -0
  36. data/spec/medic/statistics_options_spec.rb +32 -0
  37. data/spec/medic/statistics_query_spec.rb +12 -0
  38. data/spec/medic/store_spec.rb +159 -0
  39. data/spec/medic/types_spec.rb +95 -0
  40. metadata +72 -9
  41. data/spec/main_spec.rb +0 -9
@@ -0,0 +1,76 @@
1
+ module Medic
2
+ module HKConstants
3
+
4
+ ERROR_CODES = {
5
+ no_error: HKNoError,
6
+ health_data_unavailable: HKErrorHealthDataUnavailable,
7
+ health_data_restricted: HKErrorHealthDataRestricted,
8
+ invalid_argument: HKErrorInvalidArgument,
9
+ authorization_denied: HKErrorAuthorizationDenied,
10
+ authorization_not_determined: HKErrorAuthorizationNotDetermined,
11
+ database_inaccessible: HKErrorDatabaseInaccessible,
12
+ user_canceled: HKErrorUserCanceled
13
+ }
14
+
15
+ UPDATE_FREQUENCIES = {
16
+ immediate: HKUpdateFrequencyImmediate,
17
+ hourly: HKUpdateFrequencyHourly,
18
+ daily: HKUpdateFrequencyDaily,
19
+ weekly: HKUpdateFrequencyWeekly
20
+ }
21
+
22
+ AUTHORIZATION_STATUSES = {
23
+ not_determined: HKAuthorizationStatusNotDetermined,
24
+ sharing_denied: HKAuthorizationStatusSharingDenied,
25
+ sharing_authorized: HKAuthorizationStatusSharingAuthorized
26
+ }
27
+
28
+ BIOLOGICAL_SEXES = {
29
+ not_set: HKBiologicalSexNotSet,
30
+ female: HKBiologicalSexFemale,
31
+ male: HKBiologicalSexMale
32
+ }
33
+
34
+ BLOOD_TYPES = {
35
+ not_set: HKBloodTypeNotSet,
36
+ a_positive: HKBloodTypeAPositive,
37
+ a_negative: HKBloodTypeANegative,
38
+ b_positive: HKBloodTypeBPositive,
39
+ b_negative: HKBloodTypeBNegative,
40
+ a_b_positive: HKBloodTypeABPositive,
41
+ a_b_negative: HKBloodTypeABNegative,
42
+ o_positive: HKBloodTypeOPositive,
43
+ o_negative: HKBloodTypeONegative
44
+ }
45
+
46
+ SLEEP_ANALYSES = {
47
+ in_bed: HKCategoryValueSleepAnalysisInBed,
48
+ asleep: HKCategoryValueSleepAnalysisAsleep
49
+ }
50
+
51
+ def error_code(error)
52
+ error.is_a?(Symbol) ? ERROR_CODES[error] : error
53
+ end
54
+
55
+ def update_frequency(freq)
56
+ freq.is_a?(Symbol) ? UPDATE_FREQUENCIES[freq] : freq
57
+ end
58
+
59
+ def authorization_status(auth_status)
60
+ auth_status.is_a?(Symbol) ? AUTHORIZATION_STATUSES[auth_status] : auth_status
61
+ end
62
+
63
+ def biological_sex(sex)
64
+ sex.is_a?(Symbol) ? BIOLOGICAL_SEXES[sex] : sex
65
+ end
66
+
67
+ def blood_type(blood)
68
+ blood.is_a?(Symbol) ? BLOOD_TYPES[blood] : blood
69
+ end
70
+
71
+ def sleep_analysis(sleep)
72
+ sleep.is_a?(Symbol) ? SLEEP_ANALYSES[sleep] : sleep
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,29 @@
1
+ module Medic
2
+ module Interval
3
+
4
+ NUMBER_WORDS = {
5
+ 'zero' => 0, 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5,
6
+ 'six' => 6, 'seven' => 7, 'eight' => 8, 'nine' => 9, 'ten' => 10, 'eleven' => 11,
7
+ 'twelve' => 12, 'thirteen' => 13, 'fourteen' => 14, 'fifteen' => 15, 'sixteen' => 16,
8
+ 'seventeen' => 17, 'eighteen' => 18, 'nineteen' => 19, 'twenty' => 20, 'thirty' => 30,
9
+ 'fourty' => 40, 'fifty' => 50, 'sixty' => 60, 'seventy' => 70, 'eighty' => 80,
10
+ 'ninety' => 90, 'hundred' => 100
11
+ }
12
+
13
+ def interval(sym)
14
+ return sym if sym.is_a? NSDateComponents
15
+ parts = sym.to_s.gsub('_', ' ').split
16
+ component = parts.pop.chomp('s')
17
+ n = parts.map{|p| NUMBER_WORDS[p] || p.to_i}.reduce do |sum, p|
18
+ if p == 100 && sum > 0
19
+ sum * p
20
+ else
21
+ sum + p
22
+ end
23
+ end
24
+ n ||= 1
25
+ NSDateComponents.new.send("#{component}=", n)
26
+ end
27
+
28
+ end
29
+ end
data/lib/medic/medic.rb CHANGED
@@ -1,4 +1,71 @@
1
- class Medic
1
+ module Medic
2
+ class << self
2
3
 
4
+ include Medic::Finders
5
+
6
+ def available?
7
+ Medic::Store.available?
8
+ end
9
+ alias_method :is_available?, :available?
10
+
11
+ def authorize(types, block=Proc.new)
12
+ Medic::Store.shared.authorize(types, block)
13
+ end
14
+
15
+ def authorized?(sym)
16
+ Medic::Store.shared.authorized?(sym)
17
+ end
18
+ alias_method :authorized_for?, :authorized?
19
+ alias_method :is_authorized?, :authorized?
20
+ alias_method :is_authorized_for?, :authorized?
21
+
22
+ def biological_sex
23
+ Medic::Store.shared.biological_sex
24
+ end
25
+
26
+ def blood_type
27
+ Medic::Store.shared.blood_type
28
+ end
29
+
30
+ def date_of_birth
31
+ Medic::Store.shared.date_of_birth
32
+ end
33
+
34
+ def delete(hk_object, block=Proc.new)
35
+ Medic::Store.shared.delete(hk_object, block)
36
+ end
37
+ alias_method :delete_object, :delete
38
+
39
+ def save(hk_objects, block=Proc.new)
40
+ Medic::Store.shared.save(hk_objects, block)
41
+ end
42
+ alias_method :save_object, :save
43
+ alias_method :save_objects, :save
44
+
45
+ def execute(query)
46
+ Medic::Store.shared.execute(query)
47
+ end
48
+ alias_method :execute_query, :execute
49
+
50
+ def stop(query)
51
+ Medic::Store.shared.stop(query)
52
+ end
53
+ alias_method :stop_query, :stop
54
+
55
+ def enable_background_delivery(type, frequency, block=Proc.new)
56
+ Medic::Store.shared.enable_background_delivery(type, frequency, block)
57
+ end
58
+ alias_method :enable_background_delivery_for, :enable_background_delivery
59
+
60
+ def disable_background_delivery(type, block=Proc.new)
61
+ Medic::Store.shared.disable_background_delivery(type, block)
62
+ end
63
+ alias_method :disable_background_delivery_for, :disable_background_delivery
64
+
65
+ def disable_all_background_delivery(block=Proc.new)
66
+ Medic::Store.shared.disable_all_background_delivery(block)
67
+ end
68
+
69
+ end
3
70
  end
4
71
 
@@ -0,0 +1,15 @@
1
+ module Medic
2
+ class ObserverQuery < HKObserverQuery
3
+
4
+ include Medic::Types
5
+ include Medic::Predicate
6
+
7
+ def initialize(args={}, block=Proc.new)
8
+ self.initWithSampleType(object_type(args[:type]),
9
+ predicate: predicate(args),
10
+ updateHandler: block
11
+ )
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ module Medic
2
+ module Predicate
3
+
4
+ include Medic::QueryOptions
5
+
6
+ def predicate(args={})
7
+ return args if args.is_a? NSPredicate
8
+ if args[:where]
9
+ args[:where]
10
+ elsif args[:predicate]
11
+ args[:predicate]
12
+ elsif args[:sample_predicate]
13
+ args[:sample_predicate]
14
+ elsif args[:uuid] || args[:uuids]
15
+ uuids = Array(args[:uuid] || args[:uuids]).map{ |s| NSUUID.alloc.initWithUUIDString(s.to_s) }
16
+ HKQuery.predicateForObjectsWithUUIDs(uuids)
17
+ elsif args[:source] || args[:sources]
18
+ HKQuery.predicateForObjectsFromSources(Array(args[:source] || args[:sources]))
19
+ elsif args[:meta_data] && (args[:allowed_value] || args[:allowed_values])
20
+ HKQuery.predicateForObjectsWithMetadataKey(args[:meta_data].to_s, allowedValues: Array(args[:allowed_value] || args[:allowed_values]))
21
+ elsif args[:meta_data]
22
+ HKQuery.predicateForObjectsWithMetadataKey(args[:meta_data].to_s)
23
+ elsif args[:no_correlation]
24
+ HKQuery.predicateForObjectsWithNoCorrelation
25
+ elsif args[:start_date] && args[:end_date]
26
+ options = query_options(args[:options]) || HKQueryOptionNone
27
+ HKQuery.predicateForSamplesWithStartDate(args[:start_date], endDate: args[:end_date], options: options)
28
+ elsif args[:workout]
29
+ HKQuery.predicateForObjectsFromWorkout(args[:workout])
30
+ elsif args[:workout_activity_type] || args[:activity_type]
31
+ activity_type = args[:workout_activity_type] || args[:activity_type]
32
+ HKQuery.predicateForWorkoutsWithWorkoutActivityType(activity_type)
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ module Medic
2
+ module QueryOptions
3
+
4
+ QUERY_OPTIONS = {
5
+ none: HKQueryOptionNone,
6
+ strict_start_date: HKQueryOptionStrictStartDate,
7
+ strict_end_date: HKQueryOptionStrictEndDate
8
+ }
9
+
10
+ def query_options(symbols)
11
+ options = 0
12
+ Array(symbols).each do |option|
13
+ options = options | query_option(option)
14
+ end
15
+ options
16
+ end
17
+
18
+ def query_option(option)
19
+ option.is_a?(Symbol) ? QUERY_OPTIONS[option] : option
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module Medic
2
+ class SampleQuery < HKSampleQuery
3
+
4
+ include Medic::Types
5
+ include Medic::Predicate
6
+ include Medic::Sort
7
+
8
+ def initialize(args={}, block=Proc.new)
9
+ sort = args[:sort_descriptors] || args[:sort_by] || args[:sort_asc] || args[:sort]
10
+ sort = sort_descriptors(sort) if sort
11
+ sort ||= sort_descriptors(args[:sort_desc], false) if args[:sort_desc]
12
+
13
+ self.initWithSampleType(object_type(args[:type]),
14
+ predicate: predicate(args),
15
+ limit: args[:limit] || HKObjectQueryNoLimit,
16
+ sortDescriptors: sort,
17
+ resultsHandler: block
18
+ )
19
+ end
20
+
21
+ end
22
+ end
data/lib/medic/sort.rb ADDED
@@ -0,0 +1,24 @@
1
+ module Medic
2
+ module Sort
3
+
4
+ SORT_IDENTIFIERS = {
5
+ start_date: HKSampleSortIdentifierStartDate,
6
+ end_date: HKSampleSortIdentifierEndDate
7
+ }
8
+
9
+ def sort_descriptors(symbols, ascending=true)
10
+ Array(symbols).map do |sym|
11
+ if sym.is_a? NSSortDescriptor
12
+ sym
13
+ else
14
+ NSSortDescriptor.alloc.initWithKey(sort_identifier(sym), ascending: ascending)
15
+ end
16
+ end
17
+ end
18
+
19
+ def sort_identifier(sort_id)
20
+ sort_id.is_a?(Symbol) ? SORT_IDENTIFIERS[sort_id] : sort_id
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module Medic
2
+ class SourceQuery < HKSourceQuery
3
+
4
+ include Medic::Types
5
+ include Medic::Predicate
6
+
7
+ def initialize(args={}, block=Proc.new)
8
+ self.initWithSampleType(object_type(args[:type]),
9
+ samplePredicate: predicate(args),
10
+ completionHandler: block
11
+ )
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ module Medic
2
+ class StatisticsCollectionQuery < HKStatisticsCollectionQuery
3
+
4
+ include Medic::Types
5
+ include Medic::Predicate
6
+ include Medic::StatisticsOptions
7
+ include Medic::Anchor
8
+ include Medic::Interval
9
+
10
+ def initialize(args={})
11
+ self.initWithQuantityType(object_type(args[:type]),
12
+ quantitySamplePredicate: predicate(args),
13
+ options: options_for_stat_query(args[:options]),
14
+ anchorDate: anchor_for_symbol(args[:anchor_date] || args[:anchor] || args[:date] || NSDate.date),
15
+ intervalComponents: interval(args[:interval_components] || args[:interval])
16
+ )
17
+ self.initialResultsHandler = Proc.new if block_given?
18
+ self
19
+ end
20
+
21
+ alias_method :initial_results_handler, :initialResultsHandler
22
+
23
+ def initial_results_handler=(callback=Proc.new)
24
+ self.initialResultsHandler = callback
25
+ end
26
+
27
+ alias_method :statistics_update_handler, :statisticsUpdateHandler
28
+
29
+ def statistics_update_handler=(callback=Proc.new)
30
+ self.statisticsUpdateHandler = callback
31
+ end
32
+
33
+ alias_method :anchor, :anchorDate
34
+ alias_method :anchor_date, :anchorDate
35
+ alias_method :interval, :intervalComponents
36
+ alias_method :interval_components, :intervalComponents
37
+
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ module Medic
2
+ module StatisticsOptions
3
+
4
+ STATISTICS_OPTIONS = {
5
+ none: HKStatisticsOptionNone,
6
+ by_source: HKStatisticsOptionSeparateBySource,
7
+ separate_by_source: HKStatisticsOptionSeparateBySource,
8
+ average: HKStatisticsOptionDiscreteAverage,
9
+ discrete_average: HKStatisticsOptionDiscreteAverage,
10
+ min: HKStatisticsOptionDiscreteMin,
11
+ discrete_min: HKStatisticsOptionDiscreteMin,
12
+ max: HKStatisticsOptionDiscreteMax,
13
+ discrete_max: HKStatisticsOptionDiscreteMax,
14
+ sum: HKStatisticsOptionCumulativeSum,
15
+ cumulative_sum: HKStatisticsOptionCumulativeSum
16
+ }
17
+
18
+ def options_for_stat_query(symbols)
19
+ options = 0
20
+ Array(symbols).each do |option|
21
+ options = options | statistics_option(option)
22
+ end
23
+ options
24
+ end
25
+
26
+ def statistics_option(option)
27
+ option.is_a?(Symbol) ? STATISTICS_OPTIONS[option] : option
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Medic
2
+ class StatisticsQuery < HKStatisticsQuery
3
+
4
+ include Medic::Types
5
+ include Medic::Predicate
6
+ include Medic::StatisticsOptions
7
+
8
+ def initialize(args={}, block=Proc.new)
9
+ self.initWithQuantityType(object_type(args[:type]),
10
+ quantitySamplePredicate: predicate(args),
11
+ options: options_for_stat_query(args[:options]),
12
+ completionHandler: block
13
+ )
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,117 @@
1
+ module Medic
2
+ class Store < HKHealthStore
3
+
4
+ include Medic::Types
5
+ include Medic::HKConstants
6
+
7
+ def self.shared
8
+ Dispatch.once { @store ||= new }
9
+ @store
10
+ end
11
+
12
+ def self.unload
13
+ @store = nil
14
+ end
15
+
16
+ def self.available?
17
+ isHealthDataAvailable
18
+ end
19
+ singleton_class.send(:alias_method, :is_available?, :available?)
20
+
21
+ def authorize(types, block=Proc.new)
22
+ share = Array(types[:share] || types[:write]).map{ |sym| object_type(sym) }
23
+ read = Array(types[:read]).map{ |sym| object_type(sym) }
24
+
25
+ requestAuthorizationToShareTypes(NSSet.setWithArray(share), readTypes: NSSet.setWithArray(read), completion: ->(success, error){
26
+ block.call(success, error)
27
+ })
28
+ end
29
+
30
+ def authorized?(sym)
31
+ authorizationStatusForType(object_type(sym)) == HKAuthorizationStatusSharingAuthorized
32
+ end
33
+ alias_method :authorized_for?, :authorized?
34
+ alias_method :is_authorized?, :authorized?
35
+ alias_method :is_authorized_for?, :authorized?
36
+
37
+ def biological_sex
38
+ error = Pointer.new(:object)
39
+ sex = biologicalSexWithError error
40
+ if block_given?
41
+ yield BIOLOGICAL_SEXES.invert[sex.biologicalSex], error[0]
42
+ else
43
+ BIOLOGICAL_SEXES.invert[sex.biologicalSex]
44
+ end
45
+ end
46
+
47
+ def blood_type
48
+ error = Pointer.new(:object)
49
+ blood = bloodTypeWithError error
50
+ if block_given?
51
+ yield BLOOD_TYPES.invert[blood.bloodType], error[0]
52
+ else
53
+ BLOOD_TYPES.invert[blood.bloodType]
54
+ end
55
+ end
56
+
57
+ def date_of_birth
58
+ error = Pointer.new(:object)
59
+ birthday = dateOfBirthWithError error
60
+ if block_given?
61
+ yield birthday, error[0]
62
+ else
63
+ birthday
64
+ end
65
+ end
66
+
67
+ def delete(hk_object, block=Proc.new)
68
+ deleteObject(hk_object, withCompletion: ->(success, error){
69
+ block.call(success, error)
70
+ })
71
+ end
72
+ alias_method :delete_object, :delete
73
+
74
+ def save(hk_objects, block=Proc.new)
75
+ saveObjects(Array(hk_objects), withCompletion: ->(success, error){
76
+ block.call(success, error)
77
+ })
78
+ end
79
+ alias_method :save_object, :save
80
+ alias_method :save_objects, :save
81
+
82
+ # TODO: workout support
83
+ # addSamples:toWorkout:completion:
84
+
85
+ def execute(query)
86
+ executeQuery(query)
87
+ end
88
+ alias_method :execute_query, :execute
89
+
90
+ def stop(query)
91
+ stopQuery(query)
92
+ end
93
+ alias_method :stop_query, :stop
94
+
95
+ def enable_background_delivery(type, frequency, block=Proc.new)
96
+ enableBackgroundDeliveryForType(object_type(type), frequency: update_frequency(frequency), withCompletion: ->(success, error){
97
+ block.call(success, error)
98
+ })
99
+ end
100
+ alias_method :enable_background_delivery_for, :enable_background_delivery
101
+
102
+ def disable_background_delivery(type, block=Proc.new)
103
+ return disable_all_background_delivery(block) if type == :all
104
+ disableBackgroundDeliveryForType(object_type(type), withCompletion: ->(success, error){
105
+ block.call(success, error)
106
+ })
107
+ end
108
+ alias_method :disable_background_delivery_for, :disable_background_delivery
109
+
110
+ def disable_all_background_delivery(block=Proc.new)
111
+ disableAllBackgroundDeliveryWithCompletion(->(success, error){
112
+ block.call(success, error)
113
+ })
114
+ end
115
+
116
+ end
117
+ end