medic 0.0.1 → 0.0.2

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