restfully 0.6.3 → 0.7.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|