numerousapp 0.9.0 → 0.9.1

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