numerousapp 1.0.1 → 1.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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/numerousapp.rb +195 -69
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e3a77c2d75609a18065360ad1de49b131a8ce75e
4
- data.tar.gz: 476884887a6b37b9cf3c4f9bafc37124e62d996a
3
+ metadata.gz: 5bc753ba4ac5af22bec9d355a0450327a4c0415c
4
+ data.tar.gz: e5e47a6f1f2a339338bdb5fe60a66366f33fa610
5
5
  SHA512:
6
- metadata.gz: 83f23c276bbe85ae08b7f39a0b7654c3f3d27b113297e697fc59b0a060b818f1ba5f21f5bd912073bcd78649499db51124bc5944d177657c84b1072e0f34148c
7
- data.tar.gz: c7f931711ca03bbc166010e95073b95984c211f5d5e25a3ea511dab521ed03d3641a8eb92a6f0eed394d6b1d356ec52a6ddc108a247cc514ed385477d9a93fb2
6
+ metadata.gz: aba6414701c7c0ddf0e33379f3dc35a3b48070587e535be8e57ceea8247f564cc2a8a9429ac67c32dd0a028faf2ee313d548c41f18a1dbb3943ff17e9df775d1
7
+ data.tar.gz: bfbae46781b680556c3cec93a0f380d7a765443c54b9d672a6d0ca54bf4c71b4ca6bce24241a431e0b626a1f2522a31f49f0ef8f9aaf0136f853d65df149cdf1
data/lib/numerousapp.rb CHANGED
@@ -9,10 +9,10 @@
9
9
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
10
  # copies of the Software, and to permit persons to whom the Software is
11
11
  # furnished to do so, subject to the following conditions:
12
- #
12
+ #
13
13
  # The above copyright notice and this permission notice shall be included in
14
14
  # all copies or substantial portions of the Software.
15
- #
15
+ #
16
16
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
17
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
18
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -26,7 +26,7 @@
26
26
  # Numerous
27
27
  # -- server-level operations: get user parameters, create metric, etc
28
28
  #
29
- # NumerousMetric
29
+ # NumerousMetric
30
30
  # -- individual metrics: read/write/like/comment, etc
31
31
  #
32
32
  # NumerousClientInternals
@@ -72,31 +72,29 @@ end
72
72
 
73
73
  #
74
74
  # A NumerousMetricConflictError occurs when you write to a metric
75
- # and specified "only if changed" and your value
75
+ # and specified "only if changed" and your value
76
76
  # was (already) the current value
77
- #
77
+ #
78
78
  class NumerousMetricConflictError < NumerousError
79
79
  end
80
80
 
81
81
  #
82
- # == NumerousClientInternals
82
+ # == NumerousClientInternals
83
83
  #
84
84
  # Handles details of talking to the numerousapp.com server, including
85
85
  # the (basic) authentication, handling chunked APIs, json vs multipart APIs,
86
- # fixing up a few server response quirks, and so forth. It is not meant
86
+ # fixing up a few server response quirks, and so forth. It is not meant
87
87
  # for use outside of Numerous and NumerousMetric.
88
88
  #
89
89
  class NumerousClientInternals
90
90
 
91
-
92
-
93
91
  #
94
92
  # @param apiKey [String] API authentication key
95
93
  # @param server [String] Optional (keyword arg). Server name.
96
94
  # @param throttle [Proc] Optional throttle policy
97
95
  # @param throttleData [Any] Optional data for throttle
98
96
  #
99
- # @!attribute agentString
97
+ # @!attribute agentString
100
98
  # @return [String] User agent string sent to the server.
101
99
  #
102
100
  # @!attribute [r] serverName
@@ -114,7 +112,7 @@ class NumerousClientInternals
114
112
 
115
113
  @serverName = server
116
114
  @auth = { user: apiKey, password: "" }
117
- u = URI.parse("https://"+server)
115
+ u = URI.parse("https://"+server)
118
116
  @http = Net::HTTP.new(server, u.port)
119
117
  @http.use_ssl = true # always required by NumerousApp
120
118
 
@@ -135,7 +133,12 @@ class NumerousClientInternals
135
133
  # and the default policy uses the "data" (40) as the voluntary backoff point
136
134
  #
137
135
  @arbitraryMaximumTries = 10
138
- @throttlePolicy = [ThrottleDefault, 40, nil]
136
+ voluntary = { voluntary: 40}
137
+ # you can keep the dflt throttle but just alter the voluntary param, this way:
138
+ if throttleData and not throttle
139
+ voluntary = throttleData
140
+ end
141
+ @throttlePolicy = [ThrottleDefault, voluntary, nil]
139
142
  if throttle
140
143
  @throttlePolicy = [throttle, throttleData, @throttlePolicy]
141
144
  end
@@ -148,9 +151,14 @@ class NumerousClientInternals
148
151
  attr_reader :serverName, :debugLevel
149
152
  attr_reader :statistics
150
153
 
154
+ def to_s()
155
+ oid = (2 * self.object_id).to_s(16)
156
+ return "<Numerous {#{@serverName}} @ 0x#{oid}>"
157
+ end
158
+
151
159
  # Set the debug level
152
160
  #
153
- # @param [Fixnum] lvl
161
+ # @param [Fixnum] lvl
154
162
  # The desired debugging level. Greater than zero turns on debugging.
155
163
  # @return [Fixnum] the previous debugging level.
156
164
  def debug(lvl=1)
@@ -174,7 +182,7 @@ class NumerousClientInternals
174
182
 
175
183
  protected
176
184
 
177
- VersionString = '20150125-1.0.1'
185
+ VersionString = '20150215-1.0.2'
178
186
 
179
187
  MethMap = {
180
188
  GET: Net::HTTP::Get,
@@ -251,7 +259,7 @@ class NumerousClientInternals
251
259
 
252
260
 
253
261
  # ALL api exchanges with the Numerous server go through here except
254
- # for getRedirect() which is a special case (hack) for photo URLs
262
+ # for getRedirect() which is a special case (hack) for photo URLs
255
263
  #
256
264
  # Any single request/response uses this; chunked APIs use
257
265
  # the iterator classes (which in turn come back and use this repeatedly)
@@ -270,7 +278,7 @@ class NumerousClientInternals
270
278
  # in a url and it will take precedence over the basePath if any is present
271
279
  #
272
280
  # You can pass in a dictionary jdict which will be json-ified
273
- # and sent as Content-Type: application/json. Or you can pass in
281
+ # and sent as Content-Type: application/json. Or you can pass in
274
282
  # a multipart dictionary ... this is used for posting photos
275
283
  # You should not specify both jdict and multipart
276
284
  #
@@ -289,7 +297,7 @@ class NumerousClientInternals
289
297
  # need to add logic. XXX TODO XXX
290
298
  path = URI.parse(url).request_uri
291
299
  end
292
-
300
+
293
301
  rq = MethMap[api[:httpMethod]].new(path)
294
302
  rq.basic_auth(@auth[:user], @auth[:password])
295
303
  rq['user-agent'] = @agentString
@@ -333,7 +341,19 @@ class NumerousClientInternals
333
341
  @arbitraryMaximumTries.times do |attempt|
334
342
 
335
343
  @statistics[:serverRequests] += 1
344
+ t0 = Time.now
336
345
  resp = @http.request(rq)
346
+ et = Time.now - t0
347
+ # We report the elapsed round-trip time, either as a scalar (default)
348
+ # OR if you preset the :serverResponseTimes to be an array of length N
349
+ # then we keep the last N response times, thusly:
350
+ begin
351
+ times = @statistics[:serverResponseTimes]
352
+ times.insert(0, et)
353
+ times.pop()
354
+ rescue NoMethodError # just a scalar
355
+ @statistics[:serverResponseTimes] = et
356
+ end
337
357
 
338
358
  if @debugLevel > 0
339
359
  puts "Response headers:\n"
@@ -377,7 +397,7 @@ class NumerousClientInternals
377
397
  # On some requests that return "nothing" the server
378
398
  # returns {} ... on others it literally returns nothing.
379
399
  if (not resp.body) or resp.body.length == 0
380
- rj = {}
400
+ rj = {}
381
401
  else
382
402
  # this isn't supposed to happen... server bug?
383
403
  raise e
@@ -460,13 +480,13 @@ class NumerousClientInternals
460
480
  filterInfo[:prev] = filterInfo[:current]
461
481
  filterInfo[:current] = {}
462
482
  end
463
-
483
+
464
484
  list = v[api[:list]]
465
485
  nextURL = v[api[:next]]
466
486
 
467
487
  # hand them out
468
488
  if list # can be nil for a variety of reasons
469
- list.each do |i|
489
+ list.each do |i|
470
490
 
471
491
  # A note about duplicate filtering
472
492
  #
@@ -563,6 +583,9 @@ class NumerousClientInternals
563
583
 
564
584
  if attempt > 0
565
585
  stats[:throttleMultipleAttempts] += 1
586
+ if attempt > stats[:throttleMaxAttempt]
587
+ stats[:throttleMaxAttempt] = attempt
588
+ end
566
589
  end
567
590
 
568
591
  backarray = [ 2, 5, 15, 30, 60 ]
@@ -586,12 +609,12 @@ class NumerousClientInternals
586
609
  # note that some errors don't communicate rateleft so we have to
587
610
  # check for that as well (will be -1 here if wasn't sent to us)
588
611
  #
589
- # at constructor time our "throttle data" (td) was set up as the
612
+ # at constructor time our "throttle data" (td) was set up with the
590
613
  # voluntary arbitrary limit
591
- if rateleft >= 0 and rateleft < td
614
+ if rateleft >= 0 and rateleft < td[:voluntary]
592
615
  stats[:throttleVoluntaryBackoff] += 1
593
616
  # arbitrary .. 1 second if more than half left, 3 seconds if less
594
- if (rateleft*2) > td
617
+ if (rateleft*2) > td[:voluntary]
595
618
  sleep(1)
596
619
  else
597
620
  sleep(3)
@@ -631,18 +654,18 @@ end
631
654
  # Hash of <string-key, value> pairs and returned from the appropriate methods.
632
655
  #
633
656
  # For some operations the server returns only a success/failure code.
634
- # In those cases there is no useful return value from the method; the
657
+ # In those cases there is no useful return value from the method; the
635
658
  # method succeeds or else raises an exception (containing the failure code).
636
659
  #
637
660
  # For the collection operations the server returns a JSON array of dictionary
638
661
  # representations, possibly "chunked" into multiple request/response operations.
639
- # The enumerator methods (e.g., "metrics") implement lazy-fetch and hide
662
+ # The enumerator methods (e.g., "metrics") implement lazy-fetch and hide
640
663
  # the details of the chunking from you. They simply yield each individual
641
664
  # item (string-key Hash) to your block.
642
665
  #
643
666
  # === Exception handling
644
667
  #
645
- # Almost every API that interacts with the server will potentially
668
+ # Almost every API that interacts with the server will potentially
646
669
  # raise a {NumerousError} (or subclass thereof). This is not noted specifically
647
670
  # in the doc for each method unless it might be considered a "surprise"
648
671
  # (e.g., ping always returns true else raises an exception). Rescue as needed.
@@ -651,15 +674,15 @@ end
651
674
  class Numerous < NumerousClientInternals
652
675
 
653
676
  # path info for the server-level APIs: create a metric, get user info, etc
654
- APIInfo = {
677
+ APIInfo = {
655
678
  # POST to this to create a metric
656
- create: {
679
+ create: {
657
680
  path: '/v1/metrics',
658
681
  POST: { successCodes: [ 201 ] }
659
682
  },
660
683
 
661
684
  # GET a users metric collection
662
- metricsCollection: {
685
+ metricsCollection: {
663
686
  path: '/v2/users/%{userId}/metrics',
664
687
  defaults: { userId: 'me' },
665
688
  GET: { next: 'nextURL', list: 'metrics' }
@@ -721,7 +744,7 @@ class Numerous < NumerousClientInternals
721
744
  #
722
745
  # All metrics for the given user (default is your own)
723
746
  #
724
- # @param [String] userId
747
+ # @param [String] userId
725
748
  # optional - numeric id (represented as a string) of user
726
749
  # @yield [m] metrics
727
750
  # @yieldparam m [Hash] String-key representation of one metric
@@ -735,7 +758,7 @@ class Numerous < NumerousClientInternals
735
758
  #
736
759
  # All subscriptions for the given user (default is your own)
737
760
  #
738
- # @param [String] userId
761
+ # @param [String] userId
739
762
  # optional - numeric id (represented as a string) of user
740
763
  # @yield [s] subscriptions
741
764
  # @yieldparam s [Hash] String-key representation of one subscription
@@ -744,7 +767,7 @@ class Numerous < NumerousClientInternals
744
767
  def subscriptions(userId:nil, &block)
745
768
  chunkedIterator(APIInfo[:subscriptions], { userId: userId }, block)
746
769
  return self
747
- end
770
+ end
748
771
 
749
772
 
750
773
  #
@@ -754,7 +777,7 @@ class Numerous < NumerousClientInternals
754
777
  #
755
778
  # @param [Fixnum] count
756
779
  # optional - number of metrics to return
757
- # @return [Array] Array of hashes (metric string dicts). Each element
780
+ # @return [Array] Array of hashes (metric string dicts). Each element
758
781
  # represents a particular popular metric.
759
782
  #
760
783
  def mostPopular(count:nil)
@@ -765,7 +788,7 @@ class Numerous < NumerousClientInternals
765
788
  #
766
789
  # Verify connectivity to the server
767
790
  #
768
- # @return [true] Always returns true connectivity if ok.
791
+ # @return [true] Always returns true connectivity if ok.
769
792
  # Raises an exception otherwise.
770
793
  # @raise [NumerousAuthError] Your credentials are no good.
771
794
  # @raise [NumerousError] Other server (or network) errors.
@@ -781,10 +804,10 @@ class Numerous < NumerousClientInternals
781
804
  # @param label [String] Required. Label for the metric.
782
805
  # @param value [Fixnum,Float] Optional (keyword arg). Initial value.
783
806
  # @param attrs [Hash] Optional (keyword arg). Initial attributes.
784
- # @return [NumerousMetric]
807
+ # @return [NumerousMetric]
785
808
  #
786
809
  # @example Create a metric with label 'bozo' and set value to 17
787
- # nr = Numerous.new('nmrs_3vblahblah')
810
+ # nr = Numerous.new('nmrs_3vblahblah')
788
811
  # m = nr.createMetric('bozo')
789
812
  # m.write(17)
790
813
  #
@@ -810,7 +833,7 @@ class Numerous < NumerousClientInternals
810
833
  #
811
834
  # Instantiate a metric object to access a metric from the server.
812
835
  # @return [NumerousMetric] metric object
813
- # @param id [String]
836
+ # @param id [String]
814
837
  # Required. Metric ID (something like '2319923751024'). NOTE: If id
815
838
  # is bogus this will still "work" but (of course) errors will be
816
839
  # raised when you do something with the metric.
@@ -827,9 +850,8 @@ class Numerous < NumerousClientInternals
827
850
  # instead of an ID, and can even process it as a regexp.
828
851
  #
829
852
  # @param [String] labelspec The name (label) or regexp
830
- # @param [String] matchType 'FIRST' or 'BEST' or 'ONE' or 'STRING' (not regexp)
831
- # @param [Numerous] nr
832
- # The {Numerous} object that will be used to access this metric.
853
+ # @param [String] matchType 'FIRST','BEST','ONE','STRING' or 'ID'
854
+ #
833
855
  def metricByLabel(labelspec, matchType:'FIRST')
834
856
  def raiseConflict(s1,s2)
835
857
  raise NumerousMetricConflictError.new("Multiple matches", 409, [s1, s2])
@@ -840,10 +862,22 @@ class Numerous < NumerousClientInternals
840
862
  if not matchType
841
863
  matchType = 'FIRST'
842
864
  end
843
- if not ['FIRST', 'BEST', 'ONE', 'STRING'].include?(matchType)
865
+ if not ['FIRST', 'BEST', 'ONE', 'STRING', 'ID'].include?(matchType)
844
866
  raise ArgumentError
845
867
  end
846
868
 
869
+ # Having 'ID' as an option simplifies some automated use cases
870
+ # (e.g., test vectors) because you can just pair ids and matchTypes
871
+ # and simply always call ByLabel even for native (nonlabel) IDs
872
+ # We add the semantics that the result is validated as an actual ID
873
+ if matchType == 'ID'
874
+ rv = metric(labelspec)
875
+ if not rv.validate()
876
+ rv = nil
877
+ end
878
+ return rv
879
+ end
880
+
847
881
  # if you specified STRING and sent a regexp... or vice versa
848
882
  if matchType == 'STRING' and labelspec.instance_of?(Regexp)
849
883
  labelspec = labelspec.source
@@ -962,7 +996,7 @@ class Numerous < NumerousClientInternals
962
996
  # replace() bcs there might be a trailing newline on naked creds
963
997
  # (usually happens with a file or stdin)
964
998
  j[credsAPIKey] = s.sub("\n",'')
965
- end
999
+ end
966
1000
 
967
1001
  return j[credsAPIKey]
968
1002
  end
@@ -985,12 +1019,12 @@ end
985
1019
  # will return just the naked number unless you ask it for the entire dictionary)
986
1020
  #
987
1021
  # For some operations the server returns only a success/failure code.
988
- # In those cases there is no useful return value from the method; the
1022
+ # In those cases there is no useful return value from the method; the
989
1023
  # method succeeds or else raises an exception (containing the failure code).
990
1024
  #
991
1025
  # For the collection operations the server returns a JSON array of dictionary
992
1026
  # representations, possibly "chunked" into multiple request/response operations.
993
- # The enumerator methods (e.g., "events") implement lazy-fetch and hide
1027
+ # The enumerator methods (e.g., "events") implement lazy-fetch and hide
994
1028
  # the details of the chunking from you. They simply yield each individual
995
1029
  # item (string-key Hash) to your block.
996
1030
  #
@@ -1004,7 +1038,7 @@ class NumerousMetric < NumerousClientInternals
1004
1038
  # Constructor for a NumerousMetric
1005
1039
  #
1006
1040
  # @param [String] id The metric ID string.
1007
- # @param [Numerous] nr
1041
+ # @param [Numerous] nr
1008
1042
  # The {Numerous} object that will be used to access this metric.
1009
1043
  #
1010
1044
  # "id" should normally be the naked metric id (as a string).
@@ -1027,29 +1061,54 @@ class NumerousMetric < NumerousClientInternals
1027
1061
  # the presence of a '/' indicates a non-naked metric ID. This
1028
1062
  # seems a reasonable assumption given that IDs have to go into URLs
1029
1063
  #
1064
+ # "id" can be a hash representing a metric or a subscription.
1065
+ # We will take (in order) key 'metricId' or key 'id' as the id.
1066
+ # This is convenient when using the metrics() or subscriptions() iterators.
1067
+ #
1068
+ # "id" can be an integer representing a metric ID. Not recommended
1069
+ # though it's handy sometimes in cut/paste interactive testing/use.
1070
+ #
1030
1071
 
1031
1072
  def initialize(id, nr)
1073
+ actualId = nil
1032
1074
  begin
1033
- if id.include? '/'
1034
- fields = id.split('/')
1035
- if fields[-2] == "m"
1036
- id = fields[-1].to_i(36)
1037
- else
1038
- id = fields[-1]
1039
- end
1075
+ fields = id.split('/')
1076
+ if fields.length() == 1
1077
+ actualId = fields[0]
1078
+ elsif fields[-2] == "m"
1079
+ actualId = fields[-1].to_i(36)
1080
+ else
1081
+ actualId = fields[-1]
1040
1082
  end
1041
1083
  rescue NoMethodError
1042
- id = id.to_s
1043
1084
  end
1044
1085
 
1045
- @id = id
1046
- @nr = nr
1086
+ if not actualId
1087
+ # it's not a string, see if it's a hash
1088
+ actualId = id['metricId'] || id['id']
1089
+ end
1090
+
1091
+ if not actualId
1092
+ # well, see if it looks like an int
1093
+ i = id.to_i # allow this to raise exception if id bogus type here
1094
+ if i == id
1095
+ actualId = i.to_s
1096
+ end
1097
+ end
1098
+
1099
+ if not actualId
1100
+ raise ArgumentError("invalid id")
1101
+ else
1102
+ @id = actualId.to_s # defensive in case bad fmt in hash
1103
+ @nr = nr
1104
+ @cachedHash = nil
1105
+ end
1047
1106
  end
1048
1107
  attr_reader :id
1049
1108
  attr_reader :nr
1050
1109
 
1051
1110
 
1052
- APIInfo = {
1111
+ APIInfo = {
1053
1112
  # read/update/delete a metric
1054
1113
  metric: {
1055
1114
  path: '/v1/metrics/%{metricId}' ,
@@ -1151,6 +1210,67 @@ class NumerousMetric < NumerousClientInternals
1151
1210
  end
1152
1211
  private :getAPI
1153
1212
 
1213
+ # for things, such as [ ], that need a cached copy of the metric's values
1214
+ def ensureCache()
1215
+ begin
1216
+ if not @cachedHash
1217
+ ignored = read() # just reading brings cache into being
1218
+ end
1219
+ rescue NumerousError => x
1220
+ raise x # definitely pass these along
1221
+ rescue => x # anything else turn into a NumerousError
1222
+ # this is usually going to be all sorts of random low-level
1223
+ # network problems (if the network fails at the exact wrong time)
1224
+ details = { exception: x }
1225
+ raise NumerousError.new("Could not obtain metric state", 0, details)
1226
+ end
1227
+ end
1228
+ private :ensureCache
1229
+
1230
+ # access cached copy of metric via [ ]
1231
+ def [](idx)
1232
+ ensureCache()
1233
+ return @cachedHash[idx]
1234
+ end
1235
+
1236
+ # enumerator metric.each { |k, v| ... }
1237
+ def each()
1238
+ ensureCache()
1239
+ @cachedHash.each { |k, v| yield(k, v) }
1240
+ end
1241
+
1242
+ # produce the keys of a metric as an array
1243
+ def keys()
1244
+ ensureCache()
1245
+ return @cachedHash.keys
1246
+ end
1247
+
1248
+ # string representation of a metric
1249
+ def to_s()
1250
+ # there's nothing important/magic about the object id displayed; however
1251
+ # to make it match the native to_s we (believe it or not) need to multiply
1252
+ # the object_id return value by 2. This is obviously implementation specific
1253
+ # (and makes no difference to anyone; but this way it "looks right" to humans)
1254
+ objid = (2*self.object_id).to_s(16) # XXX wow lol
1255
+ rslt = "<NumerousMetric @ 0x#{objid}: "
1256
+ begin
1257
+ ensureCache()
1258
+ lbl = self['label']
1259
+ val = self['value']
1260
+ rslt += "'#{self['label']}' [#@id] = #{val}>"
1261
+ rescue NumerousError => x
1262
+ puts(x.code)
1263
+ if x.code == 400
1264
+ rslt += "**INVALID-ID** '#@id'>"
1265
+ elsif x.code == 404
1266
+ rslt += "**ID NOT FOUND** '#@id'>"
1267
+ else
1268
+ rslt += "**SERVER-ERROR** '#{x.message}'>"
1269
+ end
1270
+ end
1271
+ return rslt
1272
+ end
1273
+
1154
1274
  #
1155
1275
  # Read the current value of a metric
1156
1276
  # @param [Boolean] dictionary
@@ -1162,13 +1282,14 @@ class NumerousMetric < NumerousClientInternals
1162
1282
  def read(dictionary: false)
1163
1283
  api = getAPI(:metric, :GET)
1164
1284
  v = @nr.simpleAPI(api)
1285
+ @cachedHash = v.clone
1165
1286
  return (if dictionary then v else v['value'] end)
1166
1287
  end
1167
1288
 
1168
1289
  # "Validate" a metric object.
1169
1290
  # There really is no way to do this in any way that carries much weight.
1170
1291
  # However, if a user gives you a metricId and you'd like to know if
1171
- # that actually IS a metricId, this might be useful.
1292
+ # that actually IS a metricId, this might be useful.
1172
1293
  #
1173
1294
  # @example
1174
1295
  # someId = ... get a metric ID from someone ...
@@ -1180,7 +1301,7 @@ class NumerousMetric < NumerousClientInternals
1180
1301
  # Realize that even a valid metric can be deleted asynchronously
1181
1302
  # and thus become invalid after being validated by this method.
1182
1303
  #
1183
- # Reads the metric, catches the specific exceptions that occur for
1304
+ # Reads the metric, catches the specific exceptions that occur for
1184
1305
  # invalid metric IDs, and returns True/False. Other exceptions mean
1185
1306
  # something else went awry (server down, bad authentication, etc).
1186
1307
  # @return [Boolean] validity of the metric
@@ -1189,7 +1310,7 @@ class NumerousMetric < NumerousClientInternals
1189
1310
  ignored = read()
1190
1311
  return true
1191
1312
  rescue NumerousError => e
1192
- # bad request (400) is a completely bogus metric ID whereas
1313
+ # bad request (400) is a completely bogus metric ID whereas
1193
1314
  # not found (404) is a well-formed ID that simply does not exist
1194
1315
  if e.code == 400 or e.code == 404
1195
1316
  return false
@@ -1230,7 +1351,7 @@ class NumerousMetric < NumerousClientInternals
1230
1351
  return self
1231
1352
  end
1232
1353
 
1233
- # Enumerate the interactions (like/comment/error) of a metric.
1354
+ # Enumerate the interactions (like/comment/error) of a metric.
1234
1355
  #
1235
1356
  # @yield [i] interactions
1236
1357
  # @yieldparam i [Hash] String-key representation of one interaction.
@@ -1276,7 +1397,7 @@ class NumerousMetric < NumerousClientInternals
1276
1397
  # Obtain your subscription parameters on a given metric
1277
1398
  #
1278
1399
  # Note that normal users cannot see other user's subscriptions.
1279
- # Thus the "userId" parameter is somewhat pointless; you can only
1400
+ # Thus the "userId" parameter is somewhat pointless; you can only
1280
1401
  # ever see your own.
1281
1402
  # @param [String] userId
1282
1403
  # @return [Hash] your subscription attributes
@@ -1285,7 +1406,7 @@ class NumerousMetric < NumerousClientInternals
1285
1406
  return @nr.simpleAPI(api)
1286
1407
  end
1287
1408
 
1288
- # Subscribe to a metric.
1409
+ # Subscribe to a metric.
1289
1410
  #
1290
1411
  # See the NumerousApp API docs for what should be
1291
1412
  # in the dict. This function will fetch the current parameters
@@ -1309,7 +1430,7 @@ class NumerousMetric < NumerousClientInternals
1309
1430
  end
1310
1431
 
1311
1432
  dict.each { |k, v| params[k] = v }
1312
-
1433
+ @cachedHash = nil # bcs the subscriptions count changes
1313
1434
  api = getAPI(:subscription, :PUT, { userId: userId })
1314
1435
  return @nr.simpleAPI(api, jdict:params)
1315
1436
  end
@@ -1318,7 +1439,7 @@ class NumerousMetric < NumerousClientInternals
1318
1439
  #
1319
1440
  # @param [Fixnum|Float] newval Required. Value to be written.
1320
1441
  #
1321
- # @param [Boolean] onlyIf
1442
+ # @param [Boolean] onlyIf
1322
1443
  # Optional (keyword arg). Only creates an event at the server
1323
1444
  # if the newval is different from the current value. Raises
1324
1445
  # NumerousMetricConflictError if there is no change in value.
@@ -1345,6 +1466,7 @@ class NumerousMetric < NumerousClientInternals
1345
1466
  j['action'] = 'ADD'
1346
1467
  end
1347
1468
 
1469
+ @cachedHash = nil # will need to refresh cache on next access
1348
1470
  api = getAPI(:events, :POST)
1349
1471
  begin
1350
1472
  v = @nr.simpleAPI(api, jdict:j)
@@ -1373,7 +1495,7 @@ class NumerousMetric < NumerousClientInternals
1373
1495
  # with your updates before writing them back. If true your supplied
1374
1496
  # dictionary will become the entirety of the metric's parameters, and
1375
1497
  # any parameters you did not include in your dictionary will revert to
1376
- # their default values.
1498
+ # their default values.
1377
1499
  # @return [Hash] string-key Hash of the new metric parameters.
1378
1500
  #
1379
1501
  def update(dict, overwriteAll:false)
@@ -1381,7 +1503,8 @@ class NumerousMetric < NumerousClientInternals
1381
1503
  dict.each { |k, v| newParams[k] = v }
1382
1504
 
1383
1505
  api = getAPI(:metric, :PUT)
1384
- return @nr.simpleAPI(api, jdict:newParams)
1506
+ @cachedHash = @nr.simpleAPI(api, jdict:newParams)
1507
+ return @cachedHash.clone
1385
1508
  end
1386
1509
 
1387
1510
  # common code for writing interactions
@@ -1439,7 +1562,8 @@ class NumerousMetric < NumerousClientInternals
1439
1562
  def photo(imageDataOrReadable, mimeType:'image/jpeg')
1440
1563
  api = getAPI(:photo, :POST)
1441
1564
  mpart = { :f => imageDataOrReadable, :mimeType => mimeType }
1442
- return @nr.simpleAPI(api, multipart: mpart)
1565
+ @cachedHash = @nr.simpleAPI(api, multipart: mpart)
1566
+ return @cachedHash.clone()
1443
1567
  end
1444
1568
 
1445
1569
  # Delete the metric's photo
@@ -1447,6 +1571,7 @@ class NumerousMetric < NumerousClientInternals
1447
1571
  # but the error code will be 200/OK.
1448
1572
  # @return [nil]
1449
1573
  def photoDelete
1574
+ @cachedHash = nil # I suppose we could have just deleted the photoURL
1450
1575
  api = getAPI(:photo, :DELETE)
1451
1576
  v = @nr.simpleAPI(api)
1452
1577
  return nil
@@ -1476,9 +1601,9 @@ class NumerousMetric < NumerousClientInternals
1476
1601
 
1477
1602
  # Obtain the underlying photoURL for a metric.
1478
1603
  #
1479
- # The photoURL is available in the metrics parameters so you could
1604
+ # The photoURL is available in the metrics parameters so you could
1480
1605
  # just read(dictionary:true) and obtain it that way. However this goes
1481
- # one step further ... the URL in the metric itself still requires
1606
+ # one step further ... the URL in the metric itself still requires
1482
1607
  # authentication to fetch (it then redirects to the "real" underlying
1483
1608
  # static photo URL). This function goes one level deeper and
1484
1609
  # returns you an actual, publicly-fetchable, photo URL.
@@ -1525,6 +1650,7 @@ class NumerousMetric < NumerousClientInternals
1525
1650
  #
1526
1651
  # @return [nil]
1527
1652
  def crushKillDestroy
1653
+ @cachedHash = nil
1528
1654
  api = getAPI(:metric, :DELETE)
1529
1655
  v = @nr.simpleAPI(api)
1530
1656
  return nil
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: numerousapp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Webber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-25 00:00:00.000000000 Z
11
+ date: 2015-02-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Classes implementing the NumerousApp REST APIs for metrics. Requires
14
14
  Ruby 2.x