class Numerous

Numerous

Primary class for accessing the numerousapp server.

Constructor

You must supply an API key:
   nr = Numerous.new('nmrs_3xblahblah')

You can optionally override the built-in server name
   nr = Numerous.new('nmrs_3xblahblah', server:'test.server.com')

Server return values

For most operations the NumerousApp server returns a JSON representation of the current or modified object state. This is converted to a ruby Hash of <string-key, value> pairs and returned from the appropriate methods.

For some operations the server returns only a success/failure code. In those cases there is no useful return value from the method; the method succeeds or else raises an exception (containing the failure code).

For the collection operations the server returns a JSON array of dictionary representations, possibly “chunked” into multiple request/response operations.

The enumerator methods (e.g., “metrics”) implement lazy-fetch and hide the details of the chunking from you. They simply yield each individual item (string-key Hash) to your block.

Exception handling

Almost every API that interacts with the server will potentially raise a {NumerousError} (or subclass thereof). This is not noted specifically in the doc for each method unless it might be considered a “surprise” (e.g., ping always returns true else raises an exception). Rescue as needed.

Constants

APIInfo

path info for the server-level APIs: create a metric, get user info, etc

RaiseConflict

just a DRY shorthand for use in metricByLabel

Public Class Methods

numerousKey(s:nil, credsAPIKey:'NumerousAPIKey') click to toggle source

find an apikey from various default places @param [String] s

See documentation for details; a file name or a key or a "readable" object.

@param [String] credsAPIKey

Key to use in accessing json dict if one is found.

@return [String] the API key.

# File lib/numerousapp.rb, line 1061
def self.numerousKey(s:nil, credsAPIKey:'NumerousAPIKey')

    if not s
        # try to get from environment
        s = ENV['NUMEROUSAPIKEY']
        if not s
            return nil
        end
    end

    closeThis = nil

    if s == "@-"             # creds coming from stdin
        s = STDIN

    # see if they are in a file
    else
        begin
            if s.length() > 0         # is it a string or a file object?
                # it's stringy - if it looks like a file open it or fail
                begin
               if s.length() > 1 and s[0] == '@'
                   s = open(s[1..-1])
                   closeThis = s
               elsif s[0] == '/' or s[0] == '.'
                   s = open(s)
                   closeThis = s
                    end
                rescue
               return nil
                end
            end
        rescue NoMethodError     # it wasn't stringy, presumably it's a "readable"
        end
    end

    # well, see if whatever it is, is readable, and go with that if it is
    begin
        v = s.read()
        if closeThis
            closeThis.close()
        end
        s = v
    rescue NoMethodError
    end

    # at this point s is either a JSON or a naked cred (or bogus)
    begin
        j = JSON.parse(s)
    rescue TypeError, JSON::ParserError
        j = {}
    end


    #
    # This is kind of a hack and might hide some errors on your part
    #
    if not j.include? credsAPIKey  # this is how the naked case happens
        # replace() bcs there might be a trailing newline on naked creds
        # (usually happens with a file or stdin)
        j[credsAPIKey] = s.sub("\n",'')
    end

    return j[credsAPIKey]
end

Public Instance Methods

createMetric(label, value:nil, attrs:{}) click to toggle source

Create a brand new metric on the server.

@param label [String] Required. Label for the metric. @param value [Fixnum,Float] Optional (keyword arg). Initial value. @param attrs [Hash] Optional (keyword arg). Initial attributes. @return [NumerousMetric]

@example Create a metric with label ‘bozo’ and set value to 17

nr = Numerous.new('nmrs_3vblahblah')
m = nr.createMetric('bozo')
m.write(17)

@example Same example using the value keyword argument.

m = nr.createMetric('bozo', value:17)

@example Same example but also setting the description attribute

m = nr.createMetric('bozo', value:17, attrs:{"description" => "a clown"})
# File lib/numerousapp.rb, line 935
def createMetric(label, value:nil, attrs:{})

    api = makeAPIcontext(APIInfo[:create], :POST)

    j = attrs.clone
    j['label'] = label
    if value
        j['value'] = value
    end
    v = simpleAPI(api, jdict:j)
    return metric(v['id'])
end
metric(id) click to toggle source

Instantiate a metric object to access a metric from the server. @return [NumerousMetric] metric object @param id [String]

Required. Metric ID (something like '2319923751024'). NOTE: If id
is bogus this will still "work" but (of course) errors will be
raised when you do something with the metric.

@see createMetric createMetric @see NumerousMetric#validate validate

# File lib/numerousapp.rb, line 958
def metric(id)
    return NumerousMetric.new(id, self)
end
metricByLabel(labelspec, matchType:'FIRST') click to toggle source

Version of metric() that accepts a name (label) instead of an ID, and can even process it as a regexp.

@param [String] labelspec The name (label) or regexp @param [String] matchType ‘FIRST’,‘BEST’,‘ONE’,‘STRING’ or ‘ID’

# File lib/numerousapp.rb, line 975
def metricByLabel(labelspec, matchType:'FIRST')

    bestMatch = [ nil, 0 ]

    if not matchType
        matchType = 'FIRST'
    end
    if not ['FIRST', 'BEST', 'ONE', 'STRING', 'ID'].include?(matchType)
        raise ArgumentError
    end

    # Having 'ID' as an option simplifies some automated use cases
    # (e.g., test vectors) because you can just pair ids and matchTypes
    # and simply always call ByLabel even for native (nonlabel) IDs
    # We add the semantics that the result is validated as an actual ID
    if matchType == 'ID'
        rv = metric(labelspec)
        if not rv.validate()
            rv = nil
        end
        return rv
    end

    # if you specified STRING and sent a regexp... or vice versa
    if matchType == 'STRING' and labelspec.instance_of?(Regexp)
        labelspec = labelspec.source
    elsif matchType != 'STRING' and not labelspec.instance_of?(Regexp)
        labelspec = /#{labelspec}/
    end

    self.metrics do |m|
        if matchType == 'STRING'        # exact full match required
            if m['label'] == labelspec
                if bestMatch[0]
                    RaiseConflict.call(bestMatch[0]['label'], m['label'])
                end
                bestMatch = [ m, 1 ]   # the length is irrelevant with STRING
            end
        else
            mm = labelspec.match(m['label'])
            if mm
                if matchType == 'FIRST'
                    return self.metric(m['id'])
                elsif (matchType == 'ONE') and (bestMatch[1] > 0)
                    RaiseConflict.call(bestMatch[0]['label'], m['label'])
                end
                if mm[0].length > bestMatch[1]
                    bestMatch = [ m, mm[0].length ]
                end
            end
        end
    end
    rv = nil
    if bestMatch[0]
        rv = self.metric(bestMatch[0]['id'])
    end
end
metrics(userId:nil, &block) click to toggle source

All metrics for the given user (default is your own)

@param [String] userId

optional - numeric id (represented as a string) of user

@yield [m] metrics @yieldparam m [Hash] String-key representation of one metric @return self

# File lib/numerousapp.rb, line 868
def metrics(userId:nil, &block)
    chunkedIterator(APIInfo[:metricsCollection], { userId: userId }, block)
    return self
end
mostPopular(count:nil) click to toggle source

Obtain array of the “most popular” metrics.

@note this returns the array; it is not an Enumerator

@param [Fixnum] count

optional - number of metrics to return

@return [Array] Array of hashes (metric string dicts). Each element

represents a particular popular metric.
# File lib/numerousapp.rb, line 898
def mostPopular(count:nil)
    api = makeAPIcontext(APIInfo[:popular], :GET, {count: count})
    return simpleAPI(api)
end
ping() click to toggle source

Verify connectivity to the server

@return [true] Always returns true connectivity if ok.

Raises an exception otherwise.

@raise [NumerousAuthError] Your credentials are no good. @raise [NumerousError] Other server (or network) errors.

# File lib/numerousapp.rb, line 911
def ping
    ignored = user()
    return true      # errors raise exceptions
end
subscriptions(userId:nil, &block) click to toggle source

All subscriptions for the given user (default is your own)

@param [String] userId

optional - numeric id (represented as a string) of user

@yield [s] subscriptions @yieldparam s [Hash] String-key representation of one subscription @return self

# File lib/numerousapp.rb, line 882
def subscriptions(userId:nil, &block)
    chunkedIterator(APIInfo[:subscriptions], { userId: userId }, block)
    return self
end
user(userId:nil) click to toggle source

Obtain user attributes

@param [String] userId

optional - numeric id (represented as a string) of user

@return [Hash] user representation (string-key hash)

# File lib/numerousapp.rb, line 836
def user(userId:nil)
    api = makeAPIcontext(APIInfo[:user], :GET, {userId: userId})
    return simpleAPI(api)
end
userPhoto(imageDataOrReadable, mimeType:'image/jpeg') click to toggle source

Set the user’s photo

@note the server enforces an undocumented maximum data size.

Exceeding the limit will raise a NumerousError (HTTP 413 / Too Large)

@param [String,#read] imageDataOrReadable

Either a binary-data string of the image data or an object
with a "read" method. The entire data stream will be read.

@param [String] mimeType

Optional(keyword arg). Mime type.

@return [Hash] updated user representation (string-key hash)

# File lib/numerousapp.rb, line 853
def userPhoto(imageDataOrReadable, mimeType:'image/jpeg')
    api = makeAPIcontext(APIInfo[:user], :photo)
    mpart = { :f => imageDataOrReadable, :mimeType => mimeType }
    return simpleAPI(api, multipart: mpart)
end