restfully 0.6.3 → 0.7.0.pre
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.
- data/README.md +166 -0
- data/Rakefile +35 -35
- data/bin/restfully +68 -10
- data/lib/restfully.rb +8 -14
- data/lib/restfully/collection.rb +70 -90
- data/lib/restfully/error.rb +2 -0
- data/lib/restfully/http.rb +3 -3
- data/lib/restfully/http/error.rb +1 -20
- data/lib/restfully/http/helper.rb +49 -0
- data/lib/restfully/http/request.rb +60 -24
- data/lib/restfully/http/response.rb +55 -24
- data/lib/restfully/link.rb +32 -24
- data/lib/restfully/media_type.rb +70 -0
- data/lib/restfully/media_type/abstract_media_type.rb +162 -0
- data/lib/restfully/media_type/application_json.rb +21 -0
- data/lib/restfully/media_type/application_vnd_bonfire_xml.rb +177 -0
- data/lib/restfully/media_type/application_x_www_form_urlencoded.rb +33 -0
- data/lib/restfully/media_type/grid5000.rb +67 -0
- data/lib/restfully/media_type/wildcard.rb +27 -0
- data/lib/restfully/rack.rb +1 -0
- data/lib/restfully/rack/basic_auth.rb +26 -0
- data/lib/restfully/resource.rb +134 -197
- data/lib/restfully/session.rb +127 -70
- data/lib/restfully/version.rb +3 -0
- data/spec/fixtures/bonfire-collection-with-fragments.xml +6 -0
- data/spec/fixtures/bonfire-compute-existing.xml +43 -0
- data/spec/fixtures/bonfire-empty-collection.xml +4 -0
- data/spec/fixtures/bonfire-experiment-collection.xml +51 -0
- data/spec/fixtures/bonfire-network-collection.xml +35 -0
- data/spec/fixtures/bonfire-network-existing.xml +6 -0
- data/spec/fixtures/bonfire-root.xml +5 -0
- data/spec/fixtures/grid5000-rennes-jobs.json +988 -146
- data/spec/fixtures/grid5000-rennes.json +63 -0
- data/spec/restfully/collection_spec.rb +87 -0
- data/spec/restfully/http/helper_spec.rb +18 -0
- data/spec/restfully/http/request_spec.rb +97 -0
- data/spec/restfully/http/response_spec.rb +53 -0
- data/spec/restfully/link_spec.rb +80 -0
- data/spec/restfully/media_type/application_vnd_bonfire_xml_spec.rb +153 -0
- data/spec/restfully/media_type_spec.rb +117 -0
- data/spec/restfully/resource_spec.rb +109 -0
- data/spec/restfully/session_spec.rb +229 -0
- data/spec/spec_helper.rb +10 -9
- metadata +162 -83
- data/.document +0 -5
- data/CHANGELOG +0 -62
- data/README.rdoc +0 -146
- data/TODO.rdoc +0 -3
- data/VERSION +0 -1
- data/examples/grid5000.rb +0 -33
- data/examples/scratch.rb +0 -37
- data/lib/restfully/extensions.rb +0 -34
- data/lib/restfully/http/adapters/abstract_adapter.rb +0 -29
- data/lib/restfully/http/adapters/patron_adapter.rb +0 -16
- data/lib/restfully/http/adapters/rest_client_adapter.rb +0 -75
- data/lib/restfully/http/headers.rb +0 -20
- data/lib/restfully/parsing.rb +0 -66
- data/lib/restfully/special_array.rb +0 -5
- data/lib/restfully/special_hash.rb +0 -5
- data/restfully.gemspec +0 -114
- data/spec/collection_spec.rb +0 -120
- data/spec/fixtures/configuration_file.yml +0 -4
- data/spec/fixtures/grid5000-sites.json +0 -540
- data/spec/http/error_spec.rb +0 -18
- data/spec/http/headers_spec.rb +0 -17
- data/spec/http/request_spec.rb +0 -49
- data/spec/http/response_spec.rb +0 -19
- data/spec/http/rest_client_adapter_spec.rb +0 -35
- data/spec/link_spec.rb +0 -61
- data/spec/parsing_spec.rb +0 -40
- data/spec/resource_spec.rb +0 -320
- data/spec/restfully_spec.rb +0 -16
- data/spec/session_spec.rb +0 -171
data/lib/restfully/error.rb
CHANGED
data/lib/restfully/http.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'restfully/http/
|
1
|
+
require 'restfully/http/helper'
|
2
2
|
require 'restfully/http/error'
|
3
3
|
require 'restfully/http/request'
|
4
4
|
require 'restfully/http/response'
|
5
|
-
|
5
|
+
|
6
6
|
module Restfully
|
7
7
|
module HTTP
|
8
8
|
end
|
9
|
-
end
|
9
|
+
end
|
data/lib/restfully/http/error.rb
CHANGED
@@ -1,25 +1,6 @@
|
|
1
1
|
module Restfully
|
2
2
|
module HTTP
|
3
|
-
class Error < Restfully::Error
|
4
|
-
STATUS_CODES = {
|
5
|
-
400 => "Bad Request",
|
6
|
-
401 => "Authorization Required",
|
7
|
-
403 => "Forbiden",
|
8
|
-
406 => "Not Acceptable"
|
9
|
-
}
|
10
|
-
|
11
|
-
attr_reader :response
|
12
|
-
def initialize(response)
|
13
|
-
@response = response
|
14
|
-
response_body = response.body rescue response.raw_body
|
15
|
-
if response_body.kind_of?(Hash)
|
16
|
-
message = "#{response.status} #{response_body['title']}. #{response_body['message']}"
|
17
|
-
else
|
18
|
-
message = "#{response.status} #{STATUS_CODES[response.status] || (response_body[0..100]+"...")}"
|
19
|
-
end
|
20
|
-
super(message)
|
21
|
-
end
|
22
|
-
end
|
3
|
+
class Error < Restfully::Error; end
|
23
4
|
class ClientError < Restfully::HTTP::Error; end
|
24
5
|
class ServerError < Restfully::HTTP::Error; end
|
25
6
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Restfully
|
2
|
+
module HTTP
|
3
|
+
module Helper
|
4
|
+
|
5
|
+
def sanitize_head(h = {})
|
6
|
+
sanitized_headers = {}
|
7
|
+
h.each do |key, value|
|
8
|
+
sanitized_key = key.to_s.
|
9
|
+
downcase.
|
10
|
+
gsub(/[_-]/, ' ').
|
11
|
+
split(' ').
|
12
|
+
map{|word| word.capitalize}.
|
13
|
+
join("-")
|
14
|
+
sanitized_value = case value
|
15
|
+
when Array
|
16
|
+
value.join(", ")
|
17
|
+
else
|
18
|
+
value
|
19
|
+
end
|
20
|
+
sanitized_headers[sanitized_key] = sanitized_value
|
21
|
+
end
|
22
|
+
sanitized_headers
|
23
|
+
end
|
24
|
+
|
25
|
+
def sanitize_query(h = {})
|
26
|
+
sanitized_query = {}
|
27
|
+
h.each do |key,value|
|
28
|
+
sanitized_query[key] = stringify(value)
|
29
|
+
end
|
30
|
+
sanitized_query
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def stringify(value)
|
35
|
+
case value
|
36
|
+
when Hash
|
37
|
+
h = {}
|
38
|
+
value.each{|k,v| h[k] = stringify(v)}
|
39
|
+
h
|
40
|
+
when Array
|
41
|
+
value.map!{|v| stringify(v)}
|
42
|
+
else
|
43
|
+
value.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,42 +1,78 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
|
2
3
|
module Restfully
|
3
4
|
module HTTP
|
4
5
|
|
5
6
|
class Request
|
6
|
-
include
|
7
|
-
attr_reader :headers, :uri
|
8
|
-
attr_accessor :retries
|
7
|
+
include Helper
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
|
15
|
-
|
9
|
+
attr_reader :method, :uri, :head, :body
|
10
|
+
|
11
|
+
def initialize(session, method, path, options)
|
12
|
+
@session = session
|
13
|
+
|
14
|
+
request = options.symbolize_keys
|
15
|
+
request[:method] = method
|
16
|
+
|
17
|
+
request[:head] = sanitize_head(@session.default_headers).merge(
|
18
|
+
build_head(request)
|
19
|
+
)
|
20
|
+
|
21
|
+
request[:uri] = @session.uri_to(path)
|
22
|
+
if request[:query]
|
23
|
+
request[:uri].query_values = sanitize_query(request[:query])
|
24
|
+
end
|
25
|
+
|
26
|
+
request[:body] = if [:post, :put].include?(request[:method])
|
27
|
+
build_body(request)
|
16
28
|
end
|
17
|
-
|
18
|
-
@
|
29
|
+
|
30
|
+
@method, @uri, @head, @body = request.values_at(
|
31
|
+
:method, :uri, :head, :body
|
32
|
+
)
|
19
33
|
end
|
20
34
|
|
21
|
-
|
22
|
-
|
23
|
-
|
35
|
+
# Updates the request header and query parameters
|
36
|
+
# Returns nil if no changes were made, otherwise self.
|
37
|
+
def update!(options = {})
|
38
|
+
objects_that_may_be_updated = [@uri, @head]
|
39
|
+
old_hash = objects_that_may_be_updated.map(&:hash)
|
40
|
+
opts = options.symbolize_keys
|
41
|
+
@head.merge!(build_head(opts))
|
42
|
+
if opts[:query]
|
43
|
+
@uri.query_values = sanitize_query(opts[:query])
|
44
|
+
end
|
45
|
+
if old_hash == objects_that_may_be_updated.map(&:hash)
|
46
|
+
nil
|
24
47
|
else
|
25
|
-
|
48
|
+
self
|
26
49
|
end
|
27
50
|
end
|
28
51
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
52
|
+
def no_cache?
|
53
|
+
head['Cache-Control'] && head['Cache-Control'].include?('no-cache')
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
def build_head(options = {})
|
58
|
+
sanitize_head(
|
59
|
+
options.delete(:headers) || options.delete(:head) || {}
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_body(options = {})
|
64
|
+
if options[:body]
|
65
|
+
type = MediaType.find(options[:head]['Content-Type'])
|
66
|
+
if type.nil?
|
67
|
+
type = MediaType.find('application/x-www-form-urlencoded')
|
68
|
+
options[:head]['Content-Type'] = type.default_type
|
69
|
+
end
|
70
|
+
type.serialize(options[:body], :uri => options[:uri])
|
32
71
|
else
|
33
|
-
|
72
|
+
nil
|
34
73
|
end
|
35
74
|
end
|
36
75
|
|
37
|
-
def add_headers(headers = {})
|
38
|
-
@headers.merge!(sanitize_http_headers(headers || {}))
|
39
|
-
end
|
40
76
|
end
|
41
77
|
end
|
42
|
-
end
|
78
|
+
end
|
@@ -1,37 +1,68 @@
|
|
1
1
|
module Restfully
|
2
2
|
module HTTP
|
3
|
-
# Container for an HTTP Response. Has <tt>status</tt>, <tt>headers</tt> and <tt>body</tt> properties.
|
4
3
|
class Response
|
5
|
-
include
|
6
|
-
attr_reader :status, :headers
|
7
|
-
|
8
|
-
# <tt>body</tt>:: may be a string (that will be parsed when calling the #body function), or an object (that will be returned as is when calling the #body function)
|
9
|
-
def initialize(status, headers, body)
|
10
|
-
@status = status.to_i
|
11
|
-
@headers = sanitize_http_headers(headers)
|
12
|
-
@body = body
|
13
|
-
end
|
4
|
+
include Helper
|
14
5
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
attr_reader :io, :code, :head
|
7
|
+
|
8
|
+
def initialize(session, code, head, body)
|
9
|
+
@session = session
|
10
|
+
@io = StringIO.new
|
11
|
+
case body
|
12
|
+
when String
|
13
|
+
@io << body
|
22
14
|
else
|
23
|
-
@
|
15
|
+
body.each{|chunk| @io << chunk}
|
24
16
|
end
|
17
|
+
@io.rewind
|
18
|
+
|
19
|
+
@code = code
|
20
|
+
@head = sanitize_head(head)
|
25
21
|
end
|
26
22
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
def body
|
24
|
+
@io.rewind
|
25
|
+
@body = @io.read
|
26
|
+
@io.rewind
|
27
|
+
@body
|
28
|
+
end
|
29
|
+
|
30
|
+
def io
|
31
|
+
@io
|
32
|
+
end
|
33
|
+
|
34
|
+
def media_type
|
35
|
+
@media_type ||= begin
|
36
|
+
m = MediaType.find(head['Content-Type'])
|
37
|
+
raise Error, "Cannot find a media-type for content-type=#{head['Content-Type'].inspect}" if m.nil?
|
38
|
+
@session.logger.debug "Using media-type #{m.inspect}"
|
39
|
+
m.new(io, @session)
|
32
40
|
end
|
33
41
|
end
|
34
42
|
|
43
|
+
# TODO: we could also search for Link headers here.
|
44
|
+
def links
|
45
|
+
media_type.links
|
46
|
+
end
|
47
|
+
|
48
|
+
def property(key)
|
49
|
+
media_type.property(key)
|
50
|
+
end
|
51
|
+
|
52
|
+
def allow?(http_method)
|
53
|
+
http_method = http_method.to_sym
|
54
|
+
return true if http_method == :get
|
55
|
+
(
|
56
|
+
media_type.respond_to?(:allow?) &&
|
57
|
+
media_type.allow?(http_method)
|
58
|
+
) || (
|
59
|
+
head['Allow'] &&
|
60
|
+
head['Allow'].split(/\s*,\s*/).map{|m|
|
61
|
+
m.downcase.to_sym
|
62
|
+
}.include?(http_method)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
35
66
|
end
|
36
67
|
end
|
37
|
-
end
|
68
|
+
end
|
data/lib/restfully/link.rb
CHANGED
@@ -1,36 +1,44 @@
|
|
1
1
|
require 'uri'
|
2
2
|
module Restfully
|
3
3
|
class Link
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader :rel, :title, :href, :errors
|
9
|
-
|
4
|
+
|
5
|
+
attr_reader :rel, :title, :href, :errors, :type
|
6
|
+
|
10
7
|
def initialize(attributes = {})
|
11
|
-
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
8
|
+
attributes = attributes.symbolize_keys
|
9
|
+
@rel = attributes[:rel]
|
10
|
+
@title = attributes[:title] || @rel
|
11
|
+
@href = URI.parse(attributes[:href].to_s)
|
12
|
+
@type = attributes[:type]
|
13
|
+
@id = attributes[:id]
|
16
14
|
end
|
17
|
-
|
18
|
-
def resolvable?; @resolvable == true; end
|
19
|
-
def resolved?; @resolved == true; end
|
15
|
+
|
20
16
|
def self?; @rel == 'self'; end
|
21
|
-
|
17
|
+
|
18
|
+
def types
|
19
|
+
type.split(";")
|
20
|
+
end
|
21
|
+
|
22
22
|
def valid?
|
23
23
|
@errors = []
|
24
|
-
if
|
25
|
-
errors << "
|
26
|
-
|
27
|
-
|
28
|
-
errors << "#{rel} is not a valid link relationship."
|
24
|
+
if type.nil? || type.empty?
|
25
|
+
errors << "type cannot be blank"
|
26
|
+
elsif media_type.nil?
|
27
|
+
errors << "cannot find a MediaType for type #{type.inspect}"
|
29
28
|
end
|
30
|
-
if
|
31
|
-
errors << "
|
29
|
+
if href.nil?
|
30
|
+
errors << "href cannot be nil"
|
32
31
|
end
|
33
32
|
errors.empty?
|
34
33
|
end
|
35
|
-
|
36
|
-
|
34
|
+
|
35
|
+
def media_type
|
36
|
+
@media_type ||= MediaType.find(type)
|
37
|
+
end # def catalog
|
38
|
+
|
39
|
+
def id
|
40
|
+
title.to_s.downcase.gsub(/[^a-z]/,'_').squeeze('_').to_sym
|
41
|
+
end
|
42
|
+
|
43
|
+
end # class Link
|
44
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'restfully/media_type/abstract_media_type'
|
2
|
+
|
3
|
+
module Restfully
|
4
|
+
module MediaType
|
5
|
+
class << self
|
6
|
+
def catalog
|
7
|
+
@catalog ||= Set.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def find(*types)
|
11
|
+
return nil if types.compact.empty?
|
12
|
+
|
13
|
+
found = {}
|
14
|
+
|
15
|
+
catalog.each{ |media_type|
|
16
|
+
match = media_type.supports?(*types)
|
17
|
+
found[match] = media_type unless match.nil?
|
18
|
+
}
|
19
|
+
|
20
|
+
if found.empty?
|
21
|
+
nil
|
22
|
+
else
|
23
|
+
found.sort{|a, b| a[0].length <=> b[0].length }.last[1]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def unregister(media_type)
|
29
|
+
catalog.delete(media_type)
|
30
|
+
build_index
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def register(media_type)
|
35
|
+
if media_type.signature.empty?
|
36
|
+
raise ArgumentError, "The given MediaType (#{media_type}) has no signature"
|
37
|
+
end
|
38
|
+
if media_type.parser.nil?
|
39
|
+
raise ArgumentError, "The given MediaType (#{media_type}) has no parser"
|
40
|
+
end
|
41
|
+
unregister(media_type).catalog.add(media_type)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reset the catalog to the default list of media types
|
45
|
+
def reset
|
46
|
+
%w{
|
47
|
+
wildcard
|
48
|
+
application_json
|
49
|
+
application_x_www_form_urlencoded
|
50
|
+
grid5000
|
51
|
+
}.each do |m|
|
52
|
+
require "restfully/media_type/#{m}"
|
53
|
+
register MediaType.const_get(m.camelize)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_index
|
58
|
+
# @index = []
|
59
|
+
# catalog.each do |media_type|
|
60
|
+
#
|
61
|
+
# end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
reset
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Restfully
|
4
|
+
module MediaType
|
5
|
+
|
6
|
+
class AbstractMediaType
|
7
|
+
|
8
|
+
#
|
9
|
+
# These class functions should NOT be overwritten by descendants.
|
10
|
+
#
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def parent(method)
|
14
|
+
if superclass.respond_to?(method)
|
15
|
+
superclass.send(method)
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Hash] the hash of default options set.
|
22
|
+
def defaults
|
23
|
+
@defaults ||= (parent(:defaults) || {}).dup
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets a new <tt>value</tt> for a default <tt>attribute</tt>.
|
27
|
+
def set(attribute, value)
|
28
|
+
defaults[attribute.to_sym] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the media-type signature, i.e. the list of metia-type it
|
32
|
+
# supports.
|
33
|
+
def signature
|
34
|
+
[(defaults[:signature] || [])].flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the media-type parser.
|
38
|
+
def parser
|
39
|
+
defaults[:parser]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the media-type default signature.
|
43
|
+
def default_type
|
44
|
+
signature.first
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the first supported media-type of the signature that matches
|
48
|
+
# one of the given <tt>types</tt>.
|
49
|
+
def supports?(*types)
|
50
|
+
types.each do |type|
|
51
|
+
type = type.to_s.downcase.split(";")[0]
|
52
|
+
found = signature.find{ |s|
|
53
|
+
type =~ Regexp.new(Regexp.escape(s.downcase).gsub('\*', ".*"))
|
54
|
+
}
|
55
|
+
return found if found
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# Serialize an object into a String.
|
61
|
+
# Calls parser#dump.
|
62
|
+
def serialize(object, *args)
|
63
|
+
case object
|
64
|
+
when String
|
65
|
+
object
|
66
|
+
else
|
67
|
+
parser.dump(object, *args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Unserialize an io object into a Hash.
|
72
|
+
# Calls parser#load.
|
73
|
+
def unserialize(io, *args)
|
74
|
+
parser.load(io, *args)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
attr_reader :io, :session
|
81
|
+
|
82
|
+
# A MediaType instance takes the original io object and the current
|
83
|
+
# session object as input.
|
84
|
+
def initialize(io, session)
|
85
|
+
@io = io
|
86
|
+
@session = session
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns an array of Link objects.
|
90
|
+
# Do not overwrite directly. Overwrite #extract_links instead.
|
91
|
+
def links
|
92
|
+
@links ||= extract_links.select{|l| l.valid?}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the unserialized version of the io object.
|
96
|
+
def unserialized
|
97
|
+
@unserialized ||= self.class.unserialize(@io)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Without argument, returns the properties Hash obtained from calling
|
101
|
+
# #unserialized.
|
102
|
+
# With an argument, returns the value corresponding to that key.
|
103
|
+
#
|
104
|
+
# Should be overwritten if required.
|
105
|
+
def property(key = nil)
|
106
|
+
@properties ||= unserialized
|
107
|
+
if key
|
108
|
+
@properties[key]
|
109
|
+
else
|
110
|
+
@properties
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Should return true if the current resource represented by this
|
115
|
+
# media-type can be designated with <tt>id</tt>.
|
116
|
+
#
|
117
|
+
# Should be overwritten.
|
118
|
+
def represents?(id)
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns true if the current io object must be handled as a Collection.
|
123
|
+
#
|
124
|
+
# Should be overwritten.
|
125
|
+
def collection?
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns true if the current io object is completely loaded.
|
130
|
+
# Overwrite and return false to force a reloading if your object is just
|
131
|
+
# a URI reference.
|
132
|
+
def complete?
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
136
|
+
# An object to display on Resource#inspect.
|
137
|
+
#
|
138
|
+
# Should be overwritten.
|
139
|
+
def meta
|
140
|
+
property
|
141
|
+
end
|
142
|
+
|
143
|
+
# How to iterate over a collection
|
144
|
+
#
|
145
|
+
# Should be overwritten.
|
146
|
+
def each(*args, &block)
|
147
|
+
raise NotImplemented
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
protected
|
152
|
+
# The function that returns the array of Link object.
|
153
|
+
#
|
154
|
+
# Should be overwritten.
|
155
|
+
def extract_links
|
156
|
+
[]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|