Class: NumerousMetric
- Inherits:
-
NumerousClientInternals
- Object
- NumerousClientInternals
- NumerousMetric
- Defined in:
- lib/numerousapp.rb
Overview
NumerousMetric
Class for individual Numerous metrics
You instantiate these hanging off of a particular Numerous connection:
nr = Numerous.new('nmrs_3xblahblah')
m = nr.metric('754623094815712984')
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. A few of the methods return only one item from the Hash (e.g., read will return just the naked number unless you ask it for the entire dictionary)
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., “events”) implement lazy-fetch and hide the details of the chunking from you. They simply yield each individual item (string-key Hash) to your block.
Instance Attribute Summary collapse
-
#id ⇒ String
readonly
The metric ID string.
-
#nr ⇒ Object
readonly
Returns the value of attribute nr.
Attributes inherited from NumerousClientInternals
#agentString, #debugLevel, #serverName, #statistics
Instance Method Summary collapse
-
#[](idx) ⇒ Object
access cached copy of metric via [ ].
-
#appURL ⇒ String
the phone application generates a nmrs:// URL as a way to link to the application view of a metric (vs a web view).
-
#comment(ctext) ⇒ String
Comment on a metric.
-
#crushKillDestroy ⇒ nil
Delete a metric (permanently).
-
#delete_permission(userId) ⇒ Object
Delete a permission resource for the given user.
-
#each ⇒ Object
enumerator metric.each { |k, v| … }.
-
#event(eId = nil, before: nil) ⇒ Hash
Obtain a specific metric event from the server.
-
#eventDelete(evID) ⇒ nil
Delete an event (a value update).
-
#events {|e| ... } ⇒ NumerousMetric
Enumerate the events of a metric.
-
#get_permission(userId = nil) ⇒ Hash
Obtain a specific permission resource for the given user.
-
#initialize(id, nr = nil) ⇒ NumerousMetric
constructor
Constructor for a NumerousMetric.
-
#interaction(iId) ⇒ Hash
Obtain a specific metric interaction from the server.
-
#interactionDelete(interID) ⇒ nil
Delete an interaction (a like/comment/error).
-
#interactions {|i| ... } ⇒ NumerousMetric
Enumerate the interactions (like/comment/error) of a metric.
-
#keys ⇒ Object
produce the keys of a metric as an array.
-
#label ⇒ String
Get the label of a metric.
-
#like ⇒ String
“Like” a metric.
-
#permissions {|p| ... } ⇒ NumerousMetric
Enumerate the permissions of a metric.
-
#photo(imageDataOrReadable, mimeType: 'image/jpeg') ⇒ Hash
set the background image for a metric.
-
#photoDelete ⇒ nil
Delete the metric’s photo.
-
#photoURL ⇒ String?
Obtain the underlying photoURL for a metric.
-
#read(dictionary: false) ⇒ Fixnum|Float, Hash
Read the current value of a metric.
-
#sendError(errText) ⇒ String
Write an error to a metric.
-
#set_permission(perms, userId = nil) ⇒ Object
Set a permission for the given user.
-
#stream {|s| ... } ⇒ NumerousMetric
Enumerate the stream of a metric.
-
#subscribe(dict, userId: nil, overwriteAll: false) ⇒ Object
Subscribe to a metric.
-
#subscription(userId = nil) ⇒ Hash
Obtain your subscription parameters on a given metric.
-
#subscriptions {|s| ... } ⇒ NumerousMetric
Enumerate the subscriptions of a metric.
-
#to_s ⇒ Object
string representation of a metric.
-
#update(dict, overwriteAll: false) ⇒ Hash
Update parameters of a metric (such as “description”, “label”, etc).
-
#validate ⇒ Boolean
“Validate” a metric object.
-
#webURL ⇒ String
Get the URL for the metric’s web representation.
-
#write(newval, onlyIf: false, add: false, dictionary: false, updated: nil) ⇒ Fixnum|Float, Hash
Write a value to a metric.
Methods inherited from NumerousClientInternals
Constructor Details
#initialize(id, nr = nil) ⇒ NumerousMetric
Constructor for a NumerousMetric
“id” should normally be the naked metric id (as a string).
It can also be a nmrs: URL, e.g.:
nmrs://metric/2733614827342384
Or a ‘self’ link from the API:
https://api.numerousapp.com/metrics/2733614827342384
in either case we get the ID in the obvious syntactic way.
It can also be a metric’s web link, e.g.:
http://n.numerousapp.com/m/1x8ba7fjg72d
or the “embed” (/e/ vs /m/) variant, in which case we “just know” that the tail is a base36 encoding of the ID.
The decoding logic here makes the specific assumption that the presence of a ‘/’ indicates a non-naked metric ID. This seems a reasonable assumption given that IDs have to go into URLs
“id” can be a hash representing a metric or a subscription. We will take (in order) key ‘metricId’ or key ‘id’ as the id. This is convenient when using the metrics() or subscriptions() iterators.
“id” can be an integer representing a metric ID. Not recommended though it’s handy sometimes in cut/paste interactive testing/use.
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 |
# File 'lib/numerousapp.rb', line 1194 def initialize(id, nr=nil) # If you don't specify a Numerous we'll make one for you. # For this to work, NUMEROUSAPIKEY environment variable must exist. # m = NumerousMetric.new('234234234') is ok for simple one-shots # but note it makes a private Numerous for every metric. nr ||= Numerous.new(nil) actualId = nil begin fields = id.split('/') if fields.length() == 1 actualId = fields[0] elsif [ "m", "e" ].include? fields[-2] actualId = fields[-1].to_i(36) else actualId = fields[-1] end rescue NoMethodError end if not actualId # it's not a string, see if it's a hash actualId = id['metricId'] || id['id'] end if not actualId # well, see if it looks like an int i = id.to_i # raises exception if id bogus type here if i == id actualId = i.to_s end end if not actualId raise ArgumentError("invalid id") else @id = actualId.to_s # defensive in case bad fmt in hash @nr = nr @cachedHash = nil end end |
Instance Attribute Details
#id ⇒ String (readonly)
Returns The metric ID string.
|
|
# File 'lib/numerousapp.rb', line 1156
|
#nr ⇒ Object (readonly)
Returns the value of attribute nr.
1238 1239 1240 |
# File 'lib/numerousapp.rb', line 1238 def nr @nr end |
Instance Method Details
#[](idx) ⇒ Object
access cached copy of metric via [ ]
1385 1386 1387 1388 |
# File 'lib/numerousapp.rb', line 1385 def [](idx) ensureCache() return @cachedHash[idx] end |
#appURL ⇒ String
the phone application generates a nmrs:// URL as a way to link to the application view of a metric (vs a web view). This makes one of those for you so you don’t have to “know” the format of it.
1915 1916 1917 |
# File 'lib/numerousapp.rb', line 1915 def appURL return "nmrs://metric/" + @id end |
#comment(ctext) ⇒ String
Comment on a metric
1809 1810 1811 1812 |
# File 'lib/numerousapp.rb', line 1809 def comment(ctext) j = { 'kind' => 'comment' , 'commentBody' => ctext } return writeInteraction(j) end |
#crushKillDestroy ⇒ nil
Delete a metric (permanently). Be 100% you want this, because there is absolutely no undo.
1923 1924 1925 1926 1927 1928 |
# File 'lib/numerousapp.rb', line 1923 def crushKillDestroy @cachedHash = nil api = getAPI(:metric, :DELETE) v = @nr.simpleAPI(api) return nil end |
#delete_permission(userId) ⇒ Object
Delete a permission resource for the given user
1625 1626 1627 1628 1629 |
# File 'lib/numerousapp.rb', line 1625 def (userId) api = getAPI(:permission, :DELETE, {userId: userId}) ignored = @nr.simpleAPI(api) return nil end |
#each ⇒ Object
enumerator metric.each { |k, v| … }
1391 1392 1393 1394 |
# File 'lib/numerousapp.rb', line 1391 def each() ensureCache() @cachedHash.each { |k, v| yield(k, v) } end |
#event(eId = nil, before: nil) ⇒ Hash
Obtain a specific metric event from the server
1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 |
# File 'lib/numerousapp.rb', line 1547 def event(eId=nil, before:nil) if eId and before raise ArgumentError elsif eId api = getAPI(:event, :GET, {eventID:eId}) else # the "before" variant # if you gave us a formattable time try converting it begin ts = before.strftime('%Y-%m-%dT%H:%M:%S.') # note: we truncate, rather than round, the microseconds # for simplicity (in case usec is 999900 for example). begin ts += ("%03dZ" % (before.usec/1000)) rescue NoMethodError # in case no usec method (?) ts += '000Z' end rescue NoMethodError # just take your argument ts = before # which should be a string already end api = getAPI(:events, :at, {timestr:ts}) end return @nr.simpleAPI(api) end |
#eventDelete(evID) ⇒ nil
Deleting an event that isn’t there will raise a NumerousError but the error code will be 200/OK.
Delete an event (a value update)
1847 1848 1849 1850 1851 |
# File 'lib/numerousapp.rb', line 1847 def eventDelete(evID) api = getAPI(:event, :DELETE, {eventID:evID}) v = @nr.simpleAPI(api) return nil end |
#events {|e| ... } ⇒ NumerousMetric
Enumerate the events of a metric. Events are value updates.
1491 1492 1493 1494 |
# File 'lib/numerousapp.rb', line 1491 def events(&block) @nr.chunkedIterator(APIInfo[:events], {metricId:@id}, block) return self end |
#get_permission(userId = nil) ⇒ Hash
Obtain a specific permission resource for the given user
1601 1602 1603 1604 |
# File 'lib/numerousapp.rb', line 1601 def (userId=nil) api = getAPI(:permission, :GET, {userId: userId}) return @nr.simpleAPI(api) end |
#interaction(iId) ⇒ Hash
Obtain a specific metric interaction from the server
1578 1579 1580 1581 |
# File 'lib/numerousapp.rb', line 1578 def interaction(iId) api = getAPI(:interaction, :GET, {item:iId}) return @nr.simpleAPI(api) end |
#interactionDelete(interID) ⇒ nil
Deleting an interaction that isn’t there will raise a NumerousError but the error code will be 200/OK.
Delete an interaction (a like/comment/error)
1858 1859 1860 1861 1862 |
# File 'lib/numerousapp.rb', line 1858 def interactionDelete(interID) api = getAPI(:interaction, :DELETE, {item:interID}) v = @nr.simpleAPI(api) return nil end |
#interactions {|i| ... } ⇒ NumerousMetric
Enumerate the interactions (like/comment/error) of a metric.
1512 1513 1514 1515 |
# File 'lib/numerousapp.rb', line 1512 def interactions(&block) @nr.chunkedIterator(APIInfo[:interactions], {metricId:@id}, block) return self end |
#keys ⇒ Object
produce the keys of a metric as an array
1397 1398 1399 1400 |
# File 'lib/numerousapp.rb', line 1397 def keys() ensureCache() return @cachedHash.keys end |
#label ⇒ String
Get the label of a metric.
1897 1898 1899 1900 |
# File 'lib/numerousapp.rb', line 1897 def label v = read(dictionary:true) return v['label'] end |
#like ⇒ String
“Like” a metric
1785 1786 1787 1788 |
# File 'lib/numerousapp.rb', line 1785 def like # a like is written as an interaction return writeInteraction({ 'kind' => 'like' }) end |
#permissions {|p| ... } ⇒ NumerousMetric
Enumerate the permissions of a metric.
1532 1533 1534 1535 |
# File 'lib/numerousapp.rb', line 1532 def (&block) @nr.chunkedIterator(APIInfo[:permissionsCollection], {metricId:@id}, block) return self end |
#photo(imageDataOrReadable, mimeType: 'image/jpeg') ⇒ Hash
the server enforces an undocumented maximum data size. Exceeding the limit will raise a NumerousError (HTTP 413 / Too Large)
set the background image for a metric
1824 1825 1826 1827 1828 1829 |
# File 'lib/numerousapp.rb', line 1824 def photo(imageDataOrReadable, mimeType:'image/jpeg') api = getAPI(:photo, :POST) mpart = { :f => imageDataOrReadable, :mimeType => mimeType } @cachedHash = @nr.simpleAPI(api, multipart: mpart) return @cachedHash.clone() end |
#photoDelete ⇒ nil
Deleting a photo that isn’t there will raise a NumerousError but the error code will be 200/OK.
Delete the metric’s photo
1835 1836 1837 1838 1839 1840 |
# File 'lib/numerousapp.rb', line 1835 def photoDelete @cachedHash = nil # I suppose we could have just deleted the photoURL api = getAPI(:photo, :DELETE) v = @nr.simpleAPI(api) return nil end |
#photoURL ⇒ String?
Fetches (and discards) the entire underlying photo, because that was the easiest way to find the target URL using net/http
Obtain the underlying photoURL for a metric.
The photoURL is available in the metrics parameters so you could just read(dictionary:true) and obtain it that way. However this goes one step further … the URL in the metric itself still requires authentication to fetch (it then redirects to the “real” underlying static photo URL). This function goes one level deeper and returns you an actual, publicly-fetchable, photo URL.
1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 |
# File 'lib/numerousapp.rb', line 1878 def photoURL v = read(dictionary:true) begin phurl = v.fetch('photoURL') return @nr.getRedirect(phurl) rescue KeyError return nil end # never reached return nil end |
#read(dictionary: false) ⇒ Fixnum|Float, Hash
Read the current value of a metric
1435 1436 1437 1438 1439 1440 |
# File 'lib/numerousapp.rb', line 1435 def read(dictionary: false) api = getAPI(:metric, :GET) v = @nr.simpleAPI(api) @cachedHash = v.clone return (if dictionary then v else v['value'] end) end |
#sendError(errText) ⇒ String
Write an error to a metric
1796 1797 1798 1799 1800 1801 |
# File 'lib/numerousapp.rb', line 1796 def sendError(errText) # an error is written as an interaction thusly: # (commentBody is used for the error text) j = { 'kind' => 'error' , 'commentBody' => errText } return writeInteraction(j) end |
#set_permission(perms, userId = nil) ⇒ Object
Set a permission for the given user
1611 1612 1613 1614 1615 1616 1617 1618 1619 |
# File 'lib/numerousapp.rb', line 1611 def (perms, userId=nil) # if you don't specify a userId but DO have a userId # in the perms, use that one if (not userId) and perms.key? 'userId' userId = perms['userId'] end api = getAPI(:permission, :PUT, {userId: userId}) return @nr.simpleAPI(api, jdict:perms) end |
#stream {|s| ... } ⇒ NumerousMetric
Enumerate the stream of a metric. The stream is events and interactions merged together into a time-ordered stream.
1502 1503 1504 1505 |
# File 'lib/numerousapp.rb', line 1502 def stream(&block) @nr.chunkedIterator(APIInfo[:stream], {metricId:@id}, block) return self end |
#subscribe(dict, userId: nil, overwriteAll: false) ⇒ Object
Subscribe to a metric.
See the NumerousApp API docs for what should be in the dict. This function will fetch the current parameters and update them with the ones you supply (because the server does not like you supplying an incomplete dictionary here). You can prevent the fetch/merge via overwriteAll:true
Normal users cannot set other user’s subscriptions.
1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 |
# File 'lib/numerousapp.rb', line 1647 def subscribe(dict, userId:nil, overwriteAll:false) if overwriteAll params = {} else params = subscription(userId) end dict.each { |k, v| params[k] = v } @cachedHash = nil # bcs the subscriptions count changes api = getAPI(:subscription, :PUT, { userId: userId }) return @nr.simpleAPI(api, jdict:params) end |
#subscription(userId = nil) ⇒ Hash
Obtain your subscription parameters on a given metric
Note that normal users cannot see other user’s subscriptions. Thus the “userId” parameter is somewhat pointless; you can only ever see your own.
1590 1591 1592 1593 |
# File 'lib/numerousapp.rb', line 1590 def subscription(userId=nil) api = getAPI(:subscription, :GET, {userId: userId}) return @nr.simpleAPI(api) end |
#subscriptions {|s| ... } ⇒ NumerousMetric
Enumerate the subscriptions of a metric.
1522 1523 1524 1525 |
# File 'lib/numerousapp.rb', line 1522 def subscriptions(&block) @nr.chunkedIterator(APIInfo[:subscriptions], {metricId:@id}, block) return self end |
#to_s ⇒ Object
string representation of a metric
1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 |
# File 'lib/numerousapp.rb', line 1403 def to_s() # there's nothing important/magic about the object id displayed; however # to make it match the native to_s we (believe it or not) need to multiply # the object_id return value by 2. This is obviously implementation specific # (and makes no difference to anyone; but this way it "looks right" to humans) objid = (2*self.object_id).to_s(16) # XXX wow lol rslt = "<NumerousMetric @ 0x#{objid}: " begin ensureCache() lbl = self['label'] val = self['value'] rslt += "'#{self['label']}' [#@id] = #{val}>" rescue NumerousError => x if x.code == 400 rslt += "**INVALID-ID** '#@id'>" elsif x.code == 404 rslt += "**ID NOT FOUND** '#@id'>" else rslt += "**SERVER-ERROR** '#{x.}'>" end end return rslt end |
#update(dict, overwriteAll: false) ⇒ Hash
Update parameters of a metric (such as “description”, “label”, etc). Not to be used (won’t work) to update a metric’s value.
1763 1764 1765 1766 1767 1768 1769 1770 |
# File 'lib/numerousapp.rb', line 1763 def update(dict, overwriteAll:false) newParams = (if overwriteAll then {} else read(dictionary:true) end) dict.each { |k, v| newParams[k] = v } api = getAPI(:metric, :PUT) @cachedHash = @nr.simpleAPI(api, jdict:newParams) return @cachedHash.clone end |
#validate ⇒ Boolean
“Validate” a metric object. There really is no way to do this in any way that carries much weight. However, if a user gives you a metricId and you’d like to know if that actually IS a metricId, this might be useful.
Realize that even a valid metric can be deleted asynchronously and thus become invalid after being validated by this method.
Reads the metric, catches the specific exceptions that occur for invalid metric IDs, and returns True/False. Other exceptions mean something else went awry (server down, bad authentication, etc).
1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 |
# File 'lib/numerousapp.rb', line 1461 def validate begin ignored = read() return true rescue NumerousError => e # bad request (400) is a completely bogus metric ID whereas # not found (404) is a well-formed ID that simply does not exist if e.code == 400 or e.code == 404 return false else # anything else is a "real" error; figure out yourself raise e end end return false # this never happens end |
#webURL ⇒ String
Get the URL for the metric’s web representation
1905 1906 1907 1908 |
# File 'lib/numerousapp.rb', line 1905 def webURL v = read(dictionary:true) return v['links']['web'] end |
#write(newval, onlyIf: false, add: false, dictionary: false, updated: nil) ⇒ Fixnum|Float, Hash
Write a value to a metric.
1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 |
# File 'lib/numerousapp.rb', line 1696 def write(newval, onlyIf:false, add:false, dictionary:false, updated:nil) j = { 'value' => newval } if onlyIf != false if not [ true, 'IGNORE' ].include? onlyIf # onlyIf must be false, true, or "IGNORE" raise ArgumentError, 'onlyIf must be false, true, or "IGNORE"' end j['onlyIfChanged'] = true end if add j['action'] = 'ADD' end if updated # if you gave us a formattable time try converting it begin ts = updated.strftime('%Y-%m-%dT%H:%M:%S.') # note: we truncate, rather than round, the microseconds # for simplicity (in case usec is 999900 for example). begin ts += ("%03dZ" % (updated.usec/1000)) rescue NoMethodError # in case no usec method (?) ts += '000Z' end rescue NoMethodError # just take your argument ts = updated # which should be a string already end j['updated'] = ts end @cachedHash = nil # will need to refresh cache on next access api = getAPI(:events, :POST) begin v = @nr.simpleAPI(api, jdict:j) rescue NumerousError => e # if onlyIf was specified and the error is "conflict" # (meaning: no change), raise ConflictError specifically # or ignore it if you specified onlyIf="IGNORE" if onlyIf and e.code == 409 if onlyIf != 'IGNORE' raise NumerousMetricConflictError.new("No Change", e.details) else # forge a pseudo-result because you asked for it v = { 'value'=>newval, 'unchanged'=>true } end else raise e # never mind, plain NumerousError is fine end end return (if dictionary then v else v['value'] end) end |