Class: Api::RemoteResource

Inherits:
Object
  • Object
show all
Defined in:
lib/ocean/api_remote_resource.rb

Overview

This class represents an Ocean resource accessed by a URI.

The resource is read lazily. Retries and back off properties are available.

thing = Api::RemoteResource.new("http://api.example.com/things/1")
thing.present? => false
thing['some_attr'] => {"this" => "is", "just" => "an example"}
thing.present? => true

thing.resource_type => 'thing'
thing.status => 200
thing.status_message => "OK"
thing.response => #<an Api::Response instance>
thing.etag => "e4552f0ae517f0352f0ae56222fa0733ea52f0ae5e0"

The last raw body can be read:

thing.raw => nil or {"foo" => {...}}

Headers can be read or set:

thing.headers => {...}
thing.headers['X-Some-Header'] = "Zalagadoola"

Attributes can be read or set:

thing['foo'] = "bar"
thing['foo'] = "newbar"

Hyperlinks can be read:

thing.hyperlink['self'] => {"href"=>"http://api.example.com/things/1", 
                            "type"=>"application/json"}

These are defined for convenience:

thing.href => "http://api.example.com/things/1"
thing.type => "application/json"

The following two are equivalent:

Api::RemoteResource.get("foo")         always returns a new RemoteResource
Api::RemoteResource.new("foo").get     always returns the RemoteResource

as are:

Api::RemoteResource.get!("foo")        returns a new RemoteResource or raises an error
Api::RemoteResource.new("foo").get!    returns the RemoteResource or raises an error

thing.get! returns the RemoteResource if successful. If not, raises an exception. thing.get does the same, but always returns the RemoteResource. The remote resource can be examined to see its status.

Hyperlinks can be specified to avoid hardcoding API URIs. Simply specify the hyperlink as the first parameter to the instance versions of the HTTP accessors:

x = Api::RemoteResource.new("http://foo.com/x")
x.get(:creator)
x.get!(:creator)['username']
x.put(:confirm, body: {x:2, y:3})
x.post(:login, body: {username: "Bl0feld", password: "SPECTRE"})
x.delete(:expired)

Note the difference between:

x.put(body: {x:2, y:3})
x.put(:self, body: {x:2, y:3})
x.put(:confirm, body: {x:2, y:3})

The first two are logically equivalent: they both make a PUT request to x itself, though in the second case, the self hyperlink is explicit (the first arg defaults to :self). The third case sends a PUT request to x’s hyperlink :confirm. From this follows that the following also are equivalent:

x.get
x.get(:self)

#post, #post!, #put, #put!, #delete, and #delete! are also available and have the same hyperlink handling capabilities.

Exceptions:

GetFailed             raised when the GET to obtain the resource has failed.
ConditionalGetFailed  raised when a Conditional Get to refresh the resource has failed.
PutFailed             raised when a PUT on the resource has failed.
PostFailed            raised when a POST to the resource has failed.
DeleteFailed          raised when a DELETE of the resource has failed.
UnparseableJson       raised when the response body doesn't parse as JSON.
JsonIsNoResource      raised when the structure of the parsed JSON body is not a resource.
HyperlinkMissing      raised when a hyperlink isn't available.

To parse as a resource, the JSON must be a wrapped Ocean resource of this format:

{"thing": {
   "_links": {
     "self": {"href": "https://....", type: "application/json"},
     ...
   },
   "foo": 2,
   "bar": [1,2,3],
   ...
}

The above is a Thing resource, wrapped with its type. Attributes should appear in the inner hash, which must have at least a _links hyperlink attribute with a href and a content type.

If any of the four getters (.get!, .get, #get!, and #get) receive an Ocean collection, the instance method #collection will return an array of RemoteResources. NB: the getters will still, in all cases, return the RemoteResource itself. The collection is always made available as the value of #collection on the RemoteResource which was used to obtain it.

To refresh a resource using a conditional GET:

thing.refresh
thing.refresh!

Both return the resource itself, so you can do

thing.refresh['updated_at']

NB: this class can also be used to fetch any JSON data. E.g.:

Api::RemoteResource.get("http://example.com/anything").raw

#raw will return any raw data received, even if it wasn’t recognised as an Ocean resource and JsonIsNoResource was raised. The .get will suppress any exceptions. If #raw returns nil, you can always chack #status, #status_message, and/or #headers to determine what went wrong. After fetching non-resource data, #present? will always be false.

Defined Under Namespace

Classes: ConditionalGetFailed, DeleteFailed, GetFailed, HyperlinkMissing, JsonIsNoResource, PostFailed, PutFailed, UnparseableJson

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri, type = "application/json", args: nil, credentials: nil, x_api_token: nil, retries: 3, backoff_time: 1, backoff_rate: 0.9, backoff_max: 30) ⇒ RemoteResource

The credentials and the x_api_token, if both are left unspecified, will default to the local credentials and service token. If both are specified and different from the default values, they will be used instead. Specifying only one of them is not permitted.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/ocean/api_remote_resource.rb', line 146

def initialize(uri, type="application/json", args: nil,
                credentials: nil, x_api_token: nil,
	             retries: 3, backoff_time: 1, backoff_rate: 0.9, backoff_max: 30)
   super()
  @uri = uri
   @args = args
  @content_type = type
   @retries = retries
   @backoff_time = backoff_time
   @backoff_rate = backoff_rate
   @backoff_max = backoff_max
   @present = false
   @raw = nil
   @resource = nil
   @resource_type = nil
   @status = nil
   @status_message = nil
   @headers = nil
   @credentials = credentials
   @x_api_token = x_api_token
   @collection = false
end

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



136
137
138
# File 'lib/ocean/api_remote_resource.rb', line 136

def args
  @args
end

#backoff_maxObject (readonly)

Returns the value of attribute backoff_max.



136
137
138
# File 'lib/ocean/api_remote_resource.rb', line 136

def backoff_max
  @backoff_max
end

#backoff_rateObject (readonly)

Returns the value of attribute backoff_rate.



136
137
138
# File 'lib/ocean/api_remote_resource.rb', line 136

def backoff_rate
  @backoff_rate
end

#backoff_timeObject (readonly)

Returns the value of attribute backoff_time.



136
137
138
# File 'lib/ocean/api_remote_resource.rb', line 136

def backoff_time
  @backoff_time
end

#collectionObject

Returns the value of attribute collection.



138
139
140
# File 'lib/ocean/api_remote_resource.rb', line 138

def collection
  @collection
end

#content_typeObject (readonly)

Returns the value of attribute content_type.



136
137
138
# File 'lib/ocean/api_remote_resource.rb', line 136

def content_type
  @content_type
end

#credentialsObject (readonly)

Returns the value of attribute credentials.



137
138
139
# File 'lib/ocean/api_remote_resource.rb', line 137

def credentials
  @credentials
end

#etagObject

Returns the value of attribute etag.



138
139
140
# File 'lib/ocean/api_remote_resource.rb', line 138

def etag
  @etag
end

#headersObject

Returns the value of attribute headers.



137
138
139
# File 'lib/ocean/api_remote_resource.rb', line 137

def headers
  @headers
end

#rawObject

Returns the value of attribute raw.



137
138
139
# File 'lib/ocean/api_remote_resource.rb', line 137

def raw
  @raw
end

#resourceObject

Returns the value of attribute resource.



137
138
139
# File 'lib/ocean/api_remote_resource.rb', line 137

def resource
  @resource
end

#resource_typeObject

Returns the value of attribute resource_type.



137
138
139
# File 'lib/ocean/api_remote_resource.rb', line 137

def resource_type
  @resource_type
end

#responseObject

Returns the value of attribute response.



138
139
140
# File 'lib/ocean/api_remote_resource.rb', line 138

def response
  @response
end

#retriesObject (readonly)

Returns the value of attribute retries.



136
137
138
# File 'lib/ocean/api_remote_resource.rb', line 136

def retries
  @retries
end

#statusObject

Returns the value of attribute status.



137
138
139
# File 'lib/ocean/api_remote_resource.rb', line 137

def status
  @status
end

#status_messageObject

Returns the value of attribute status_message.



138
139
140
# File 'lib/ocean/api_remote_resource.rb', line 138

def status_message
  @status_message
end

#uriObject (readonly)

Returns the value of attribute uri.



136
137
138
# File 'lib/ocean/api_remote_resource.rb', line 136

def uri
  @uri
end

#x_api_tokenObject (readonly)

Returns the value of attribute x_api_token.



137
138
139
# File 'lib/ocean/api_remote_resource.rb', line 137

def x_api_token
  @x_api_token
end

Class Method Details

.get(*args) ⇒ Object

Class method to retrieve a resource. Will not raise exceptions if problems occur, but will always return the resource itself. The args are passed directly to new.



268
269
270
271
272
# File 'lib/ocean/api_remote_resource.rb', line 268

def self.get(*args)
  rr = new(*args)
  x = rr.get! rescue nil
  x || rr
end

.get!(*args) ⇒ Object

Class method to retrieve a resource. Will raise exceptions if problems occur, otherwise returns the resource itself. The args are passed directly to new.



260
261
262
# File 'lib/ocean/api_remote_resource.rb', line 260

def self.get!(*args)
  new(*args).send :_retrieve
end

Instance Method Details

#[](key) ⇒ Object

Returns a resource attribute. If the resource isn’t present, get! will be used to retrieve it.



180
181
182
183
# File 'lib/ocean/api_remote_resource.rb', line 180

def [](key)
  get! unless present?
  resource[key]
end

#[]=(key, value) ⇒ Object

Sets a resource attribute. The resource will not be retrieved if not present.



188
189
190
# File 'lib/ocean/api_remote_resource.rb', line 188

def []=(key, value)
  resource[key] = value
end

#delete(hlink = nil, args: nil) ⇒ Object

Instance method to do a DELETE to a resource or any of its hyperlinks. Will not raise exceptions if they occur, but will always return the resource itself. A missing hyperlink, though, will always raise a HyperlinkMissing exception.

Raises:



397
398
399
400
401
402
403
404
# File 'lib/ocean/api_remote_resource.rb', line 397

def delete(hlink=nil, args: nil)
  get
  hlink = (hlink || 'self').to_s
  hl_data = hyperlink[hlink]
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
  _destroy(hl_data['href'], args: args) rescue nil
  self
end

#delete!(hlink = nil, args: nil) ⇒ Object

Instance method to do a DELETE to a resource or any of its hyperlinks. Will raise exceptions if they occur, otherwise will return the resource itself.

Raises:



383
384
385
386
387
388
389
390
# File 'lib/ocean/api_remote_resource.rb', line 383

def delete!(hlink=nil, args: nil)
  get!
  hlink = (hlink || 'self').to_s
  hl_data = hyperlink[hlink]
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
  _destroy(hl_data['href'], args: args)
  self
end

#get(hlink = nil, args: nil) ⇒ Object

Instance method to GET a resource or any of its hyperlinks. Will not raise exceptions if problems occur, but will always return the resource itself. A missing hyperlink, though, will always raise a HyperlinkMissing exception.



297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/ocean/api_remote_resource.rb', line 297

def get(hlink=nil, args: nil)
  get! rescue nil
  hlink = hlink.to_s if hlink
  if !args && (!hlink || hlink == 'self')
    self
  else
    hl_data = hyperlink[hlink]
    raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
    RemoteResource.get(hl_data['href'], args: args, retries: retries, backoff_time: backoff_time, 
                                        backoff_rate: backoff_rate, backoff_max: backoff_max)
  end
end

#get!(hlink = nil, args: nil) ⇒ Object

Instance method to GET a resource or any of its hyperlinks. Will raise exceptions if they occur, otherwise will return the resource itself.



279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/ocean/api_remote_resource.rb', line 279

def get!(hlink=nil, args: nil)
  _retrieve unless present?
  hlink = hlink.to_s if hlink
  if !args && (!hlink || hlink == 'self')
    self
  else
    hl_data = hyperlink[hlink]
    raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
    RemoteResource.get!(hl_data['href'], args: args, retries: retries, backoff_time: backoff_time, 
                                         backoff_rate: backoff_rate, backoff_max: backoff_max)
  end
end

#hrefObject

Returns resources own href. The resource will not be retrieved if not present.



202
203
204
# File 'lib/ocean/api_remote_resource.rb', line 202

def href
  hyperlink['self']['href']
end

Returns the hash of hyperlinks. The resource will not be retrieved if not present.



195
196
197
# File 'lib/ocean/api_remote_resource.rb', line 195

def hyperlink
  self['_links']
end

#post(hlink = nil, args: nil, body: {}) ⇒ Object

Instance method to do a POST to a resource or any of its hyperlinks. Will not raise exceptions if they occur, but will always return the resource itself. A missing hyperlink, though, will always raise a HyperlinkMissing exception.

Raises:



370
371
372
373
374
375
376
# File 'lib/ocean/api_remote_resource.rb', line 370

def post(hlink=nil, args: nil, body: {})
  get
  hlink = (hlink || !args && 'self').to_s
  hl_data = hyperlink[hlink]
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
  _create(hl_data['href'], body, args: args) rescue nil
end

#post!(hlink = nil, args: nil, body: {}) ⇒ Object

Instance method to do a POST to a resource or any of its hyperlinks. Will raise exceptions if they occur, otherwise will return the resource itself.

Raises:



357
358
359
360
361
362
363
# File 'lib/ocean/api_remote_resource.rb', line 357

def post!(hlink=nil, args: nil, body: {})
  get!
  hlink = (hlink || !args && 'self').to_s
  hl_data = hyperlink[hlink]
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
  _create(hl_data['href'], body, args: args)
end

#present?Boolean

True if the resource has been read successfully.

Returns:

  • (Boolean)


172
173
174
# File 'lib/ocean/api_remote_resource.rb', line 172

def present?
  @present
end

#put(hlink = nil, args: nil, body: {}) ⇒ Object

Instance method to do a PUT to a resource or any of its hyperlinks. Will not raise exceptions if they occur, but will always return the resource itself. A missing hyperlink, though, will always raise a HyperlinkMissing exception.



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/ocean/api_remote_resource.rb', line 336

def put(hlink=nil, args: nil, body: {})
  get
  hlink = hlink.to_s if hlink
  if !args && (!hlink || hlink == 'self')
    _modify(body) rescue nil
    self
  else
    hl_data = hyperlink[hlink]
    raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
    hl_res = RemoteResource.new(hl_data['href'], retries: retries, backoff_time: backoff_time, 
                                                 backoff_rate: backoff_rate, backoff_max: backoff_max)
    hl_res.send :_modify, body, args: args
    hl_res
  end
end

#put!(hlink = nil, args: nil, body: {}) ⇒ Object

Instance method to do a PUT to a resource or any of its hyperlinks. Will raise exceptions if they occur, otherwise will return the resource itself.



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/ocean/api_remote_resource.rb', line 315

def put!(hlink=nil, args: nil, body: {})
  get!
  hlink = hlink.to_s if hlink
  if !args && (!hlink || hlink == 'self')
    _modify(body)
    self
  else
    hl_data = hyperlink[hlink]
    raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
    hl_res = RemoteResource.new(hl_data['href'], retries: retries, backoff_time: backoff_time, 
                                                 backoff_rate: backoff_rate, backoff_max: backoff_max)
    hl_res.send :_modify, body, args: args
    hl_res
  end
end

#refreshObject

If the resource is present and has received an ETag in previous requests, will perform a Conditional GET to update the local representation. If the resource isn’t present or has no ETag, a normal #get will be done. The resource will always be returned.



427
428
429
430
431
432
433
# File 'lib/ocean/api_remote_resource.rb', line 427

def refresh
  if present? && etag.present?
    (_conditional_get rescue nil) || self
  else
    get
  end
end

#refresh!Object

If the resource is present and has received an ETag in previous requests, will perform a Conditional GET to update the local representation. If the resource isn’t present or has no ETag, a normal #get! will be done. If no exception is raised, the resource itself is returned.



413
414
415
416
417
418
419
# File 'lib/ocean/api_remote_resource.rb', line 413

def refresh!
  if present? && etag.present?
    _conditional_get || self
  else
    get!
  end
end

#typeObject

Returns resources own content type. The resource will not be retrieved if not present.



209
210
211
# File 'lib/ocean/api_remote_resource.rb', line 209

def type
  hyperlink['self']['type']
end