numerousapp 0.9.0 → 0.9.1

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 +425 -83
  3. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d5d927781f67b4b8929965a109a0f1e3398ef79
4
- data.tar.gz: 338338af020ac429ce9e8cfe3c2a76cd425f48ba
3
+ metadata.gz: 01cd1c5e889cbf214c82da8f8901ea435602708e
4
+ data.tar.gz: 8772cd439ca04a25860ddf8c47e4f836c7d46606
5
5
  SHA512:
6
- metadata.gz: aedd071bc1f66a16f68c1a361e799cb6e6b89c1603dcce55ee9af101e0fe5a0d29b7332bc7424e2f6257dc962e87d68624fc30551e04dcbee21e5809cec81a4d
7
- data.tar.gz: b260a3ec6fbe3b8731713f8467e28e1094b43c98700e4fc29e71eaff65b7ecafd7eee4e3440b161e7c84ee194dc10374747f037d09d3edceef710f021f11fd8a
6
+ metadata.gz: 15582f2a034419114a327c990326a08db2455f1c0f4a30eb8e3cb15bcfbd89e677d93598566eae20754316a8c4d7cf887703f23e05acf33f29f7bf77b4f25f6c
7
+ data.tar.gz: 5c47dd449c711afce6e558b69e5fe752a331ce55e0374e19b2f7ee3e2bf6745050a613d7bdb44feeb7884c898963be8db8aa99c4783b976d0237868469bca6e4
data/lib/numerousapp.rb CHANGED
@@ -38,7 +38,22 @@ require 'json'
38
38
  require 'net/http'
39
39
  require 'uri'
40
40
 
41
+ #
42
+ # NumerousError exceptions indicate errors from the server
43
+ #
41
44
  class NumerousError < StandardError
45
+ #
46
+ # @!attribute msg
47
+ # human-targeted error message as a string
48
+ #
49
+ # @!attribute code
50
+ # HTTP error code (e.g., 404)
51
+ #
52
+ # @!attribute details
53
+ # hash containing more ad-hoc information, most usefully :id which
54
+ # is the URL that was used in the request to the server
55
+ #
56
+
42
57
  def initialize(msg, code, details)
43
58
  super(msg)
44
59
  @code = code
@@ -48,20 +63,47 @@ class NumerousError < StandardError
48
63
  attr_accessor :code, :details
49
64
  end
50
65
 
66
+ #
67
+ # A NumerousAuthError occurs when the server rejects your credentials,
68
+ # Usually means your apiKey is (or has become) bad.
69
+ #
51
70
  class NumerousAuthError < NumerousError
52
71
  end
53
72
 
73
+ #
74
+ # A NumerousMetricConflictError occurs when you write to a metric
75
+ # and specified "only if changed" and your value
76
+ # was (already) the current value
77
+ #
54
78
  class NumerousMetricConflictError < NumerousError
55
79
  end
56
80
 
57
81
  #
58
- # This class is not meant for public consumption but it subclassed
59
- # into Numerous and NumerousMetric. It encapsultes all the details
60
- # of talking to the numerous server, dealing with chunked APIs, etc.
82
+ # == NumerousClientInternals
83
+ #
84
+ # Handles details of talking to the numerousapp.com server, including
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
87
+ # for use outside of Numerous and NumerousMetric.
61
88
  #
62
89
  class NumerousClientInternals
63
90
 
91
+ #
92
+ # @param apiKey [String] API authentication key
93
+ # @param server [String] Optional (keyword arg). Server name.
94
+ #
95
+ # @!attribute agentString
96
+ # @return [String] User agent string sent to the server.
97
+ #
98
+ # @!attribute [r] serverName
99
+ # @return [String] FQDN of the target NumerousApp server.
100
+ #
101
+ # @!attribute [r] debugLevel
102
+ # @return [Fixnum] Current debugging level; use debug() method to change.
103
+ #
64
104
  def initialize(apiKey, server:'api.numerousapp.com')
105
+
106
+
65
107
  @serverName = server
66
108
  @auth = { user: apiKey, password: "" }
67
109
  u = URI.parse("https://"+server)
@@ -74,7 +116,13 @@ class NumerousClientInternals
74
116
  @debugLevel = 0
75
117
  end
76
118
  attr_accessor :agentString
119
+ attr_reader :serverName, :debugLevel
77
120
 
121
+ # Set the debug level
122
+ #
123
+ # @param [Fixnum] lvl
124
+ # The desired debugging level. Greater than zero turns on debugging.
125
+ # @return [Fixnum] the previous debugging level.
78
126
  def debug(lvl=1)
79
127
  prev = @debugLevel
80
128
  @debugLevel = lvl
@@ -88,7 +136,7 @@ class NumerousClientInternals
88
136
 
89
137
  protected
90
138
 
91
- VersionString = '20141223.1x'
139
+ VersionString = '20141224.1'
92
140
 
93
141
  MethMap = {
94
142
  GET: Net::HTTP::Get,
@@ -96,7 +144,7 @@ class NumerousClientInternals
96
144
  PUT: Net::HTTP::Put,
97
145
  DELETE: Net::HTTP::Delete
98
146
  }
99
-
147
+ private_constant :MethMap
100
148
 
101
149
  #
102
150
  # This gathers all the relevant information for a given API
@@ -298,6 +346,9 @@ class NumerousClientInternals
298
346
 
299
347
  # generic iterator for chunked APIs
300
348
  def chunkedIterator(info, subs={}, block)
349
+ # if you didn't specify a block... there's no point in doing anything
350
+ if not block; return nil; end
351
+
301
352
  api = makeAPIcontext(info, :GET, subs)
302
353
  list = []
303
354
  nextURL = api[:basePath]
@@ -323,12 +374,47 @@ class NumerousClientInternals
323
374
  end
324
375
  end
325
376
 
377
+ #
378
+ # == Numerous
379
+ #
380
+ # Primary class for accessing the numerousapp server.
381
+ #
382
+ # === Constructor
383
+ #
384
+ # You must supply an API key:
385
+ # nr = Numerous.new('nmrs_3xblahblah')
386
+ #
387
+ # You can optionally override the built-in server name
388
+ # nr = Numerous.new('nmrs_3xblahblah', server:'test.server.com')
389
+ #
390
+ # === Server return values
391
+ #
392
+ # For most operations the NumerousApp server returns a JSON representation
393
+ # of the current or modified object state. This is converted to a ruby
394
+ # Hash of <string-key, value> pairs and returned from the appropriate methods.
395
+ #
396
+ # For some operations the server returns only a success/failure code.
397
+ # In those cases there is no useful return value from the method; the
398
+ # method succeeds or else raises an exception (containing the failure code).
399
+ #
400
+ # For the collection operations the server returns a JSON array of dictionary
401
+ # representations, possibly "chunked" into multiple request/response operations.
402
+ # The enumerator methods (e.g., "metrics") implement lazy-fetch and hide
403
+ # the details of the chunking from you. They simply yield each individual
404
+ # item (string-key Hash) to your block.
405
+ #
406
+ # === Exception handling
407
+ #
408
+ # Almost every API that interacts with the server will potentially
409
+ # raise a {NumerousError} (or subclass thereof). This is not noted specifically
410
+ # in the doc for each method unless it might be considered a "surprise"
411
+ # (e.g., ping always returns true else raises an exception). Rescue as needed.
412
+ #
326
413
 
327
414
  class Numerous < NumerousClientInternals
328
415
 
329
416
  # path info for the server-level APIs: create a metric, get user info, etc
330
-
331
- APIInfo = {
417
+ APIInfo = {
332
418
  # POST to this to create a metric
333
419
  create: {
334
420
  path: '/v1/metrics',
@@ -363,57 +449,116 @@ class Numerous < NumerousClientInternals
363
449
  # no entry needed for GET because no special codes etc
364
450
  }
365
451
  }
452
+ private_constant :APIInfo
366
453
 
367
-
368
- # return User info (Default is yourself)
454
+ #
455
+ # Obtain user attributes
456
+ #
457
+ # @param [String] userId
458
+ # optional - numeric id (represented as a string) of user
459
+ # @return [Hash] user representation (string-key hash)
460
+ #
369
461
  def user(userId:nil)
370
462
  api = makeAPIcontext(APIInfo[:user], :GET, {userId: userId})
371
463
  return simpleAPI(api)
372
464
  end
373
465
 
374
- # set the user's photo
375
- # imageDataOrReadable is the raw binary image data OR
376
- # an object with a read method (e.g., an open file)
377
- # mimeType defaults to image/jpeg but you can specify as needed
378
466
  #
379
- # NOTE: The server enforces a size limit (I don't know it)
380
- # and you will get an HTTP "Too Large" error if you exceed it
467
+ # Set the user's photo
468
+ #
469
+ # @note the server enforces an undocumented maximum data size.
470
+ # Exceeding the limit will raise a NumerousError (HTTP 413 / Too Large)
471
+ # @param [String,#read] imageDataOrReadable
472
+ # Either a binary-data string of the image data or an object
473
+ # with a "read" method. The entire data stream will be read.
474
+ # @param [String] mimeType
475
+ # Optional(keyword arg). Mime type.
476
+ # @return [Hash] updated user representation (string-key hash)
477
+ #
381
478
  def userPhoto(imageDataOrReadable, mimeType:'image/jpeg')
382
479
  api = makeAPIcontext(APIInfo[:user], :photo)
383
480
  mpart = { :f => imageDataOrReadable, :mimeType => mimeType }
384
481
  return simpleAPI(api, multipart: mpart)
385
482
  end
386
483
 
387
- # various iterators for invoking a block on various collections.
388
- # The "return self" is convention for chaining though not clear how useful
389
-
390
- # metrics: all metrics for the given user (default is your own)
484
+ #
485
+ # All metrics for the given user (default is your own)
486
+ #
487
+ # @param [String] userId
488
+ # optional - numeric id (represented as a string) of user
489
+ # @yield [m] metrics
490
+ # @yieldparam m [Hash] String-key representation of one metric
491
+ # @return self
492
+ #
391
493
  def metrics(userId:nil, &block)
392
494
  chunkedIterator(APIInfo[:metricsCollection], { userId: userId }, block)
393
495
  return self
394
496
  end
395
497
 
396
- # subscriptions: all the subscriptions for the given user
498
+ #
499
+ # All subscriptions for the given user (default is your own)
500
+ #
501
+ # @param [String] userId
502
+ # optional - numeric id (represented as a string) of user
503
+ # @yield [s] subscriptions
504
+ # @yieldparam s [Hash] String-key representation of one subscription
505
+ # @return self
506
+ #
397
507
  def subscriptions(userId:nil, &block)
398
508
  chunkedIterator(APIInfo[:subscriptions], { userId: userId }, block)
399
509
  return self
400
510
  end
401
511
 
402
512
 
403
-
404
- # most popular metrics ... not an iterator
513
+ #
514
+ # Obtain array of the "most popular" metrics.
515
+ #
516
+ # @note this returns the array; it is not an Enumerator
517
+ #
518
+ # @param [Fixnum] count
519
+ # optional - number of metrics to return
520
+ # @return [Array] Array of hashes (metric string dicts). Each element
521
+ # represents a particular popular metric.
522
+ #
405
523
  def mostPopular(count:nil)
406
524
  api = makeAPIcontext(APIInfo[:popular], :GET, {count: count})
407
525
  return simpleAPI(api)
408
526
  end
409
527
 
410
- # test/verify connectivity to the server
528
+ #
529
+ # Verify connectivity to the server
530
+ #
531
+ # @return [true] Always returns true connectivity if ok.
532
+ # Raises an exception otherwise.
533
+ # @raise [NumerousAuthError] Your credentials are no good.
534
+ # @raise [NumerousError] Other server (or network) errors.
535
+ #
411
536
  def ping
412
537
  ignored = user()
413
- return true # errors throw exceptions
538
+ return true # errors raise exceptions
414
539
  end
415
540
 
541
+ #
542
+ # Create a brand new metric on the server.
543
+ #
544
+ # @param label [String] Required. Label for the metric.
545
+ # @param value [Fixnum,Float] Optional (keyword arg). Initial value.
546
+ # @param attrs [Hash] Optional (keyword arg). Initial attributes.
547
+ # @return [NumerousMetric]
548
+ #
549
+ # @example Create a metric with label 'bozo' and set value to 17
550
+ # nr = Numerous.new('nmrs_3vblahblah')
551
+ # m = nr.createMetric('bozo')
552
+ # m.write(17)
553
+ #
554
+ # @example Same example using the value keyword argument.
555
+ # m = nr.createMetric('bozo', value:17)
556
+ #
557
+ # @example Same example but also setting the description attribute
558
+ # m = nr.createMetric('bozo', value:17, attrs:{"description" => "a clown"})
559
+ #
416
560
  def createMetric(label, value:nil, attrs:{})
561
+
417
562
  api = makeAPIcontext(APIInfo[:create], :POST)
418
563
 
419
564
  j = attrs.clone
@@ -425,29 +570,74 @@ class Numerous < NumerousClientInternals
425
570
  return metric(v['id'])
426
571
  end
427
572
 
428
- # instantiate a metric object to access a numerous metric
429
- # -- this is NOT creating a metric on the server; this is how you
430
- # access a metric that already exists
573
+ #
574
+ # Instantiate a metric object to access a metric from the server.
575
+ # @return [NumerousMetric] metric object
576
+ # @param id [String]
577
+ # Required. Metric ID (something like '2319923751024'). NOTE: If id
578
+ # is bogus this will still "work" but (of course) errors will be
579
+ # raised when you do something with the metric.
580
+ # @see #createMetric createMetric
581
+ # @see NumerousMetric#validate validate
582
+ #
431
583
  def metric(id)
432
584
  return NumerousMetric.new(id, self)
433
585
  end
434
586
 
435
587
  end
436
588
 
589
+ #
590
+ # == NumerousMetric
591
+ #
592
+ # Class for individual Numerous metrics
593
+ #
594
+ # You instantiate these hanging off of a particular Numerous connection:
595
+ # nr = Numerous.new('nmrs_3xblahblah')
596
+ # m = nr.metric('754623094815712984')
597
+ #
598
+ # For most operations the NumerousApp server returns a JSON representation
599
+ # of the current or modified object state. This is converted to a ruby
600
+ # Hash of <string-key, value> pairs and returned from the appropriate methods.
601
+ # A few of the methods return only one item from the Hash (e.g., read
602
+ # will return just the naked number unless you ask it for the entire dictionary)
603
+ #
604
+ # For some operations the server returns only a success/failure code.
605
+ # In those cases there is no useful return value from the method; the
606
+ # method succeeds or else raises an exception (containing the failure code).
607
+ #
608
+ # For the collection operations the server returns a JSON array of dictionary
609
+ # representations, possibly "chunked" into multiple request/response operations.
610
+ # The enumerator methods (e.g., "events") implement lazy-fetch and hide
611
+ # the details of the chunking from you. They simply yield each individual
612
+ # item (string-key Hash) to your block.
613
+ #
437
614
 
438
615
  class NumerousMetric < NumerousClientInternals
616
+ #
617
+ # @!attribute [r] id
618
+ # @return [String] The metric ID string.
619
+ #
620
+
621
+ # Constructor for a NumerousMetric
622
+ #
623
+ # @param [String] id The metric ID string.
624
+ # @param [Numerous] nr
625
+ # The {Numerous} object that will be used to access this metric.
439
626
  def initialize(id, nr)
440
627
  @id = id
441
628
  @nr = nr
442
629
  end
443
- attr_accessor :id
630
+ attr_reader :id
444
631
 
445
- # could have just made an accessor, but I prefer it this way for this one
632
+ #
633
+ # Obtain the {Numerous} server object associated with a metric.
634
+ # @return [Numerous]
635
+ #
446
636
  def getServer()
447
637
  return @nr
448
638
  end
449
639
 
450
- APIInfo = {
640
+ APIInfo = {
451
641
  # read/update/delete a metric
452
642
  metric: {
453
643
  path: '/v1/metrics/%{metricId}' ,
@@ -538,6 +728,7 @@ class NumerousMetric < NumerousClientInternals
538
728
  }
539
729
 
540
730
  }
731
+ private_constant :APIInfo
541
732
 
542
733
  # small wrapper to always supply the metricId substitution
543
734
  def getAPI(a, mx, args={})
@@ -545,7 +736,14 @@ class NumerousMetric < NumerousClientInternals
545
736
  end
546
737
  private :getAPI
547
738
 
548
-
739
+ #
740
+ # Read the current value of a metric
741
+ # @param [Boolean] dictionary
742
+ # If true the entire metric will be returned as a string-key Hash;
743
+ # else (false/default) a bare number (Fixnum or Float) is returned.
744
+ # @return [Fixnum|Float] if dictionary is false (or defaulted).
745
+ # @return [Hash] if dictionary is true.
746
+ #
549
747
  def read(dictionary: false)
550
748
  api = getAPI(:metric, :GET)
551
749
  v = @nr.simpleAPI(api)
@@ -555,12 +753,22 @@ class NumerousMetric < NumerousClientInternals
555
753
  # "Validate" a metric object.
556
754
  # There really is no way to do this in any way that carries much weight.
557
755
  # However, if a user gives you a metricId and you'd like to know if
558
- # that actually IS a metricId, this might be useful. Realize that
559
- # even a valid metric can be deleted out from under and become invalid.
756
+ # that actually IS a metricId, this might be useful.
757
+ #
758
+ # @example
759
+ # someId = ... get a metric ID from someone ...
760
+ # m = nr.metric(someId)
761
+ # if not m.validate
762
+ # puts "#{someId} is not a valid metric"
763
+ # end
764
+ #
765
+ # Realize that even a valid metric can be deleted asynchronously
766
+ # and thus become invalid after being validated by this method.
560
767
  #
561
768
  # Reads the metric, catches the specific exceptions that occur for
562
769
  # invalid metric IDs, and returns True/False. Other exceptions mean
563
770
  # something else went awry (server down, bad authentication, etc).
771
+ # @return [Boolean] validity of the metric
564
772
  def validate
565
773
  begin
566
774
  ignored = read()
@@ -578,44 +786,107 @@ class NumerousMetric < NumerousClientInternals
578
786
  end
579
787
 
580
788
 
581
- # define the events, stream, interactions, and subscriptions methods
582
- # All have same pattern so we use some of Ruby's awesome meta hackery
583
- %w(events stream interactions subscriptions).each do |w|
584
- define_method(w) do | &block |
585
- @nr.chunkedIterator(APIInfo[w.to_sym], {metricId:@id}, block)
586
- return self
587
- end
789
+ #
790
+ # So I had a really nifty %w/.each define_method hack here to generate
791
+ # these methods that follow a simple pattern. Then trying to figure out
792
+ # how to YARD document them was daunting. If it's easy someone needs to
793
+ # show me (I get the impression it's possible with some run time magic
794
+ # but it's just too hard to figure out for now). So, here we go instead...
795
+ #
796
+
797
+ # Enumerate the events of a metric. Events are value updates.
798
+ #
799
+ # @yield [e] events
800
+ # @yieldparam e [Hash] String-key representation of one metric.
801
+ # @return [NumerousMetric] self
802
+ def events(&block)
803
+ @nr.chunkedIterator(APIInfo[:events], {metricId:@id}, block)
804
+ return self
588
805
  end
589
806
 
590
- # read a single event or single interaction
591
- %w(event interaction).each do |w|
592
- define_method(w) do | evId |
593
- api = getAPI(w.to_sym, :GET, {eventID:evId})
594
- return @nr.simpleAPI(api)
595
- end
807
+ # Enumerate the stream of a metric. The stream is events and
808
+ # interactions merged together into a time-ordered stream.
809
+ #
810
+ # @yield [s] stream
811
+ # @yieldparam s [Hash] String-key representation of one stream item.
812
+ # @return [NumerousMetric] self
813
+ def stream(&block)
814
+ @nr.chunkedIterator(APIInfo[:stream], {metricId:@id}, block)
815
+ return self
816
+ end
817
+
818
+ # Enumerate the interactions (like/comment/error) of a metric.
819
+ #
820
+ # @yield [i] interactions
821
+ # @yieldparam i [Hash] String-key representation of one interaction.
822
+ # @return [NumerousMetric] self
823
+ def interactions(&block)
824
+ @nr.chunkedIterator(APIInfo[:interactions], {metricId:@id}, block)
825
+ return self
826
+ end
827
+
828
+ # Enumerate the subscriptions of a metric.
829
+ #
830
+ # @yield [s] subscriptions
831
+ # @yieldparam s [Hash] String-key representation of one subscription.
832
+ # @return [NumerousMetric] self
833
+ def subscriptions(&block)
834
+ @nr.chunkedIterator(APIInfo[:subscriptions], {metricId:@id}, block)
835
+ return self
836
+ end
837
+
838
+
839
+ # Obtain a specific metric event from the server
840
+ #
841
+ # @param [String] eId The specific event ID
842
+ # @return [Hash] The string-key hash of the event
843
+ # @raise [NumerousError] Not found (.code will be 404)
844
+ #
845
+ def event(eId)
846
+ api = getAPI(:event, :GET, {eventID:eId})
847
+ return @nr.simpleAPI(api)
596
848
  end
597
849
 
850
+ # Obtain a specific metric interaction from the server
851
+ #
852
+ # @param [String] iId The specific interaction ID
853
+ # @return [Hash] The string-key hash of the interaction
854
+ # @raise [NumerousError] Not found (.code will be 404)
855
+ #
856
+ def interaction(iId)
857
+ api = getAPI(:interaction, :GET, {item:iId})
858
+ return @nr.simpleAPI(api)
859
+ end
598
860
 
599
- # This is an individual subscription -- namely, yours.
600
- # normal users can never see anything other than their own
601
- # subscription so there is really no point in ever supplying
602
- # the userId parameter (the default is all you can ever use)
861
+ # Obtain your subscription parameters on a given metric
862
+ #
863
+ # Note that normal users cannot see other user's subscriptions.
864
+ # Thus the "userId" parameter is somewhat pointless; you can only
865
+ # ever see your own.
866
+ # @param [String] userId
867
+ # @return [Hash] your subscription attributes
603
868
  def subscription(userId=nil)
604
869
  api = getAPI(:subscription, :GET, {userId: userId})
605
870
  return @nr.simpleAPI(api)
606
871
  end
607
872
 
608
- # Subscribe to a metric. See the API docs for what should be
873
+ # Subscribe to a metric.
874
+ #
875
+ # See the NumerousApp API docs for what should be
609
876
  # in the dict. This function will fetch the current parameters
610
877
  # and update them with the ones you supply (because the server
611
878
  # does not like you supplying an incomplete dictionary here).
612
- # While in some cases this might be a bit of extra overhead
613
- # it doesn't really matter because how often do you do this...
614
- # You can, however, stop that with overwriteAll=True
879
+ # You can prevent the fetch/merge via overwriteAll:true
615
880
  #
616
- # NOTE that you really can only subscribe yourself, so there
617
- # really isn't much point in specifying a userId
618
- def subscribe(dict, userId:nil, overwriteAll:False)
881
+ # Normal users cannot set other user's subscriptions.
882
+ # @param [Hash] dict
883
+ # string-key hash of subscription parameters
884
+ # @param [String] userId
885
+ # Optional (keyword arg). UserId to subscribe.
886
+ # @param [Boolean] overwriteAll
887
+ # Optional (keyword arg). If true, dict is sent without reading
888
+ # the current parameters and merging them.
889
+ def subscribe(dict, userId:nil, overwriteAll:false)
619
890
  if overwriteAll
620
891
  params = {}
621
892
  else
@@ -628,12 +899,33 @@ class NumerousMetric < NumerousClientInternals
628
899
  return @nr.simpleAPI(api, jdict:params)
629
900
  end
630
901
 
631
- # write a value to a metric.
902
+ # Write a value to a metric.
903
+ #
904
+ # @param [Fixnum|Float] newval Required. Value to be written.
905
+ #
906
+ # @param [Boolean] onlyIf
907
+ # Optional (keyword arg). Only creates an event at the server
908
+ # if the newval is different from the current value. Raises
909
+ # NumerousMetricConflictError if there is no change in value.
910
+ # WARNING: Not atomic at the server; it is still possible to get
911
+ # a success code result from this even if your value was already
912
+ # the current value (under simultaneous-update scenarios).
913
+ #
914
+ # @param [Boolean] add
915
+ # Optional (keyword arg). Sends the "action: ADD" attribute which
916
+ # causes the server to ADD newval to the current metric value.
917
+ # WARNING: As of Dec 2014 there is still a bug in the server that
918
+ # causes ADD operations to not be atomic. Until that bug is fixed
919
+ # the semantics of ADD are no different than if you did a read/write
920
+ # (as two separate operations) yourself.
921
+ # @param [Boolean] dictionary
922
+ # If true the entire metric will be returned as a string-key Hash;
923
+ # else (false/default) the bare number (Fixnum or Float) for the
924
+ # resulting new value is returned.
925
+ # @return [Fixnum|Float] if dictionary is false (or defaulted). The new
926
+ # value of the metric is returned as a bare number.
927
+ # @return [Hash] if dictionary is true the entire new metric is returned.
632
928
  #
633
- # onlyIf=true sends the "only if it changed" feature of the NumerousAPI.
634
- # -- throws NumerousMetricConflictError if no change
635
- # add=true sends the "action: ADD" (the value is added to the metric)
636
- # dictionary=true returns the full dictionary results.
637
929
  def write(newval, onlyIf:false, add:false, dictionary:false)
638
930
  j = { 'value' => newval }
639
931
  if onlyIf
@@ -651,7 +943,7 @@ class NumerousMetric < NumerousClientInternals
651
943
  # if onlyIf was specified and the error is "conflict"
652
944
  # (meaning: no change), raise ConflictError specifically
653
945
  if onlyIf and e.code == 409
654
- raise NumerousMetricConflictError.new(e.details, 0, "No Change")
946
+ raise NumerousMetricConflictError.new("No Change", 0, e.details)
655
947
  else
656
948
  raise e # never mind, plain NumerousError is fine
657
949
  end
@@ -660,6 +952,20 @@ class NumerousMetric < NumerousClientInternals
660
952
  return (if dictionary then v else v['value'] end)
661
953
  end
662
954
 
955
+ # Update parameters of a metric (such as "description", "label", etc).
956
+ # Not to be used (won't work) to update a metric's value.
957
+ #
958
+ # @param [Hash] dict
959
+ # string-key Hash of the parameters to be updated.
960
+ # @param [Boolean] overwriteAll
961
+ # Optional (keyword arg). If false (default), this method will first
962
+ # read the current metric parameters from the server and merge them
963
+ # with your updates before writing them back. If true your supplied
964
+ # dictionary will become the entirety of the metric's parameters, and
965
+ # any parameters you did not include in your dictionary will revert to
966
+ # their default values.
967
+ # @return [Hash] string-key Hash of the new metric parameters.
968
+ #
663
969
  def update(dict, overwriteAll:false)
664
970
  newParams = (if overwriteAll then {} else read(dictionary:true) end)
665
971
  dict.each { |k, v| newParams[k] = v }
@@ -679,6 +985,8 @@ class NumerousMetric < NumerousClientInternals
679
985
  #
680
986
  # "Like" a metric
681
987
  #
988
+ # @return [String] The ID of the resulting interaction (the "like")
989
+ #
682
990
  def like
683
991
  # a like is written as an interaction
684
992
  return writeInteraction({ 'kind' => 'like' })
@@ -687,6 +995,9 @@ class NumerousMetric < NumerousClientInternals
687
995
  #
688
996
  # Write an error to a metric
689
997
  #
998
+ # @param [String] errText The error text to write.
999
+ # @return [String] The ID of the resulting interaction (the "error")
1000
+ #
690
1001
  def sendError(errText)
691
1002
  # an error is written as an interaction thusly:
692
1003
  # (commentBody is used for the error text)
@@ -695,7 +1006,10 @@ class NumerousMetric < NumerousClientInternals
695
1006
  end
696
1007
 
697
1008
  #
698
- # Simply comment on a metric
1009
+ # Comment on a metric
1010
+ #
1011
+ # @param [String] ctext The comment text to write.
1012
+ # @return [String] The ID of the resulting interaction (the "comment")
699
1013
  #
700
1014
  def comment(ctext)
701
1015
  j = { 'kind' => 'comment' , 'commentBody' => ctext }
@@ -703,50 +1017,68 @@ class NumerousMetric < NumerousClientInternals
703
1017
  end
704
1018
 
705
1019
  # set the background image for a metric
706
- # imageDataOrReadable is the raw binary image data OR
707
- # an object with a read method (e.g., an open file)
708
- # mimeType defaults to image/jpeg but you can specify as needed
1020
+ # @note the server enforces an undocumented maximum data size.
1021
+ # Exceeding the limit will raise a NumerousError (HTTP 413 / Too Large)
1022
+ # @param [String,#read] imageDataOrReadable
1023
+ # Either a binary-data string of the image data or an object
1024
+ # with a "read" method. The entire data stream will be read.
1025
+ # @param [String] mimeType
1026
+ # Optional(keyword arg). Mime type.
1027
+ # @return [Hash] updated metric representation (string-key hash)
709
1028
  #
710
- # NOTE: The server enforces a size limit (I don't know it)
711
- # and you will get an HTTP "Too Large" error if you exceed it
712
1029
  def photo(imageDataOrReadable, mimeType:'image/jpeg')
713
1030
  api = getAPI(:photo, :POST)
714
1031
  mpart = { :f => imageDataOrReadable, :mimeType => mimeType }
715
1032
  return @nr.simpleAPI(api, multipart: mpart)
716
1033
  end
717
1034
 
718
- # various deletion methods.
719
- # NOTE: If you try to delete something that isn't there, you will
720
- # see an exception but the "error" code will be 200/OK.
721
- # Semantically, the delete "works" in that case (i.e., the invariant
722
- # that the thing should be gone after this invocation is, in fact, true).
723
- # Nevertheless, I let the exception come through to you in case you
724
- # want to know if this happened. This note applies to all deletion methods.
725
-
1035
+ # Delete the metric's photo
1036
+ # @note Deleting a photo that isn't there will raise a NumerousError
1037
+ # but the error code will be 200/OK.
1038
+ # @return [nil]
726
1039
  def photoDelete
727
1040
  api = getAPI(:photo, :DELETE)
728
1041
  v = @nr.simpleAPI(api)
729
1042
  return nil
730
1043
  end
731
1044
 
1045
+ # Delete an event (a value update)
1046
+ # @note Deleting an event that isn't there will raise a NumerousError
1047
+ # but the error code will be 200/OK.
1048
+ # @param [String] evID ID (string) of the event to be deleted.
1049
+ # @return [nil]
732
1050
  def eventDelete(evID)
733
1051
  api = getAPI(:event, :DELETE, {eventID:evID})
734
1052
  v = @nr.simpleAPI(api)
735
1053
  return nil
736
1054
  end
737
1055
 
1056
+ # Delete an interaction (a like/comment/error)
1057
+ # @note Deleting an interaction that isn't there will raise a NumerousError
1058
+ # but the error code will be 200/OK.
1059
+ # @param [String] interID ID (string) of the interaction to be deleted.
1060
+ # @return [nil]
738
1061
  def interactionDelete(interID)
739
1062
  api = getAPI(:interaction, :DELETE, {item:interID})
740
1063
  v = @nr.simpleAPI(api)
741
1064
  return nil
742
1065
  end
743
1066
 
744
- # the photoURL returned by the server in the metrics parameters
745
- # still requires authentication to fetch (it then redirects to the "real"
1067
+ # Obtain the underlying photoURL for a metric.
1068
+ #
1069
+ # The photoURL is available in the metrics parameters so you could
1070
+ # just read(dictionary:true) and obtain it that way. However this goes
1071
+ # one step further ... the URL in the metric itself still requires
1072
+ # authentication to fetch (it then redirects to the "real" underlying
746
1073
  # static photo URL). This function goes one level deeper and
747
- # returns you an actual, publicly-fetchable, photo URL. I have not
748
- # yet figured out how to tease this out without doing the full-on GET
749
- # (using HEAD on a photo is rejected by the server)
1074
+ # returns you an actual, publicly-fetchable, photo URL.
1075
+ #
1076
+ # IMPLEMENTATION NOTE: Fetches (and discards) the entire underlying photo,
1077
+ # because that was the easiest way to tease out the target URL from
1078
+ # the Net:HTTP library.
1079
+ #
1080
+ # @return [String, nil] URL. If there is no photo returns nil.
1081
+ #
750
1082
  def photoURL
751
1083
  v = read(dictionary:true)
752
1084
  begin
@@ -762,17 +1094,27 @@ class NumerousMetric < NumerousClientInternals
762
1094
  # some convenience functions ... but all these do is query the
763
1095
  # server (read the metric) and return the given field... you could
764
1096
  # do the very same yourself. So I only implemented a few useful ones.
1097
+
1098
+ # Get the label of a metric.
1099
+ #
1100
+ # @return [String] The metric label.
765
1101
  def label
766
1102
  v = read(dictionary:true)
767
1103
  return v['label']
768
1104
  end
769
1105
 
1106
+ # Get the URL for the metric's web representation
1107
+ #
1108
+ # @return [String] URL.
770
1109
  def webURL
771
1110
  v = read(dictionary:true)
772
1111
  return v['links']['web']
773
1112
  end
774
1113
 
775
- # be 100% sure, because this cannot be undone. Deletes a metric
1114
+ # Delete a metric (permanently). Be 100% you want this, because there
1115
+ # is absolutely no undo.
1116
+ #
1117
+ # @return [nil]
776
1118
  def crushKillDestroy
777
1119
  api = getAPI(:metric, :DELETE)
778
1120
  v = @nr.simpleAPI(api)
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: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Webber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-23 00:00:00.000000000 Z
11
+ date: 2014-12-24 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
@@ -43,3 +43,4 @@ signing_key:
43
43
  specification_version: 4
44
44
  summary: NumerousApp API
45
45
  test_files: []
46
+ has_rdoc: