Class: Restfully::Resource

Inherits:
Object show all
Defined in:
lib/restfully/resource.rb

Overview

This class represents a Resource, which can be accessed and manipulated via HTTP methods.

The #load method must have been called on the resource before trying to access its attributes or links.

Direct Known Subclasses

Collection

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri, session, options = {}) ⇒ Resource

Description

Creates a new Resource.

uri

a URI object representing the URI of the resource (complete, absolute or relative URI)

session

an instantiated Restfully::Session object

options

a hash of options (see below)

Options

:title

an optional title for the resource



26
27
28
29
30
31
32
# File 'lib/restfully/resource.rb', line 26

def initialize(uri, session, options = {})
  options = options.symbolize_keys
  @uri = uri.kind_of?(URI) ? uri : URI.parse(uri.to_s)
  @session = session
  @title = options[:title]
  reset
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object



59
60
61
62
63
64
65
66
# File 'lib/restfully/resource.rb', line 59

def method_missing(method, *args)
  if link = @links[method.to_s]
    session.logger.debug "Loading link #{method}, args=#{args.inspect}"
    link.load(*args)
  else
    super(method, *args)
  end
end

Instance Attribute Details

#executed_requestsObject (readonly)

Returns the value of attribute executed_requests.



11
12
13
# File 'lib/restfully/resource.rb', line 11

def executed_requests
  @executed_requests
end

Returns the value of attribute links.



11
12
13
# File 'lib/restfully/resource.rb', line 11

def links
  @links
end

#propertiesObject (readonly)

Returns the value of attribute properties.



11
12
13
# File 'lib/restfully/resource.rb', line 11

def properties
  @properties
end

#sessionObject (readonly)

Returns the value of attribute session.



11
12
13
# File 'lib/restfully/resource.rb', line 11

def session
  @session
end

#titleObject (readonly)

Returns the value of attribute title.



11
12
13
# File 'lib/restfully/resource.rb', line 11

def title
  @title
end

#uriObject (readonly)

Returns the value of attribute uri.



11
12
13
# File 'lib/restfully/resource.rb', line 11

def uri
  @uri
end

Instance Method Details

#[](key) ⇒ Object

Description

Returns the value corresponding to the specified key, among the list of resource properties

Usage

resource["uid"]
=> "rennes"


51
52
53
# File 'lib/restfully/resource.rb', line 51

def [](key)
  @properties[key]
end

#delete(options = {}) ⇒ Object

Description

Executes a DELETE request on the resource, and returns true if successful. If the response status is different from 2xx or 3xx, raises an HTTP::ClientError or HTTP::ServerError.

options

list of options to pass to the request (see below)

Options

:query

a hash of query parameters to pass along the request. E.g. : resource.delete(:query => => “value1”)

:headers

a hash of HTTP headers to pass along the request. E.g. : resource.delete(:headers => => ‘application/json’)

Raises:

  • (NotImplementedError)


155
156
157
158
159
160
161
# File 'lib/restfully/resource.rb', line 155

def delete(options = {})
  options = options.symbolize_keys
  raise NotImplementedError, "The DELETE method is not allowed for this resource." unless http_methods.include?('DELETE')
  response = session.delete(self.uri, options) # raises an exception if there is an error
  stale!
  (200..399).include?(response.status)
end

#http_methodsObject

Description

Returns the list of allowed HTTP methods on the resource.

Usage

resource.http_methods    
=> ['GET', 'POST']


174
175
176
177
# File 'lib/restfully/resource.rb', line 174

def http_methods
  reload if executed_requests['GET'].nil? || executed_requests['GET']['headers'].nil? || executed_requests['GET']['headers'].empty?
  (executed_requests['GET']['headers']['Allow'] || "GET").split(/,\s*/)
end

#inspect(*args) ⇒ Object



183
184
185
# File 'lib/restfully/resource.rb', line 183

def inspect(*args)
  @properties.inspect(*args)
end

#load(options = {}) ⇒ Object

Description

Executes a GET request on the resource, and populate the list of its properties and links

options

list of options to pass to the request (see below)

Options

:reload

if set to true, a GET request will be triggered even if the resource has already been loaded [default=false]

:query

a hash of query parameters to pass along the request. E.g. : resource.load(:query => => (Time.now-3600).to_i, :to => Time.now.to_i)

:headers

a hash of HTTP headers to pass along the request. E.g. : resource.load(:headers => => ‘application/json’)

:body

if you already have the unserialized response body of this resource, you may pass it so that the GET request is not triggered.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/restfully/resource.rb', line 81

def load(options = {})
  options = options.symbolize_keys
  force_reload = !!options.delete(:reload)
  stale! unless !force_reload && (request = executed_requests['GET']) && request['options'] == options && request['body']
  if stale?
    reset
    if !force_reload && options[:body]
      body = options[:body]
      headers = {}
    else
      response = session.get(uri, options)
      body = response.body
      headers = response.headers
    end
    executed_requests['GET'] = {
      'options' => options, 
      'body' =>  body,
      'headers' => headers
    }
    executed_requests['GET']['body'].each do |key, value|
      populate_object(key, value)
    end
    @status = :loaded
  end
  self
end

#pretty_print(pp) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/restfully/resource.rb', line 187

def pretty_print(pp)
  pp.text "#<#{self.class}:0x#{self.object_id.to_s(16)}"
  pp.text " uid=#{self['uid'].inspect}" if self.class == Resource
  pp.nest 2 do
    pp.breakable
    pp.text "@uri="
    uri.pretty_print(pp)
    if @links.length > 0
      pp.breakable
      pp.text "LINKS"
      pp.nest 2 do
        @links.to_a.each_with_index do |(key, value), i|
          pp.breakable
          pp.text "@#{key}=#<#{value.class}:0x#{value.object_id.to_s(16)}>"
          pp.text "," if i < @links.length-1
        end
      end  
    end
    if @properties.length > 0
      pp.breakable
      pp.text "PROPERTIES"
      pp.nest 2 do
        @properties.to_a.each_with_index do |(key, value), i|
          pp.breakable
          pp.text "#{key.inspect}=>"
          value.pretty_print(pp)
          pp.text "," if i < @properties.length-1
        end
      end
    end
    yield pp if block_given?
  end
  pp.text ">"
end

#reloadObject

Convenience function to make a resource.load(:reload => true)



109
110
111
112
113
# File 'lib/restfully/resource.rb', line 109

def reload
  current_options = executed_requests['GET']['options'] rescue {}
  stale!
  self.load(current_options.merge(:reload => true))
end

#resetObject

Resets all the inner objects of the resource (you must call #load if you want to repopulate the resource).



36
37
38
39
40
41
42
# File 'lib/restfully/resource.rb', line 36

def reset
  @executed_requests = Hash.new
  @links = Hash.new
  @properties = Hash.new
  @status = :stale
  self
end

#respond_to?(method, *args) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/restfully/resource.rb', line 55

def respond_to?(method, *args)
  @links.has_key?(method.to_s) || super(method, *args)
end

#stale!Object



164
# File 'lib/restfully/resource.rb', line 164

def stale!; @status = :stale;  end

#stale?Boolean

Returns:

  • (Boolean)


165
# File 'lib/restfully/resource.rb', line 165

def stale?; @status == :stale; end

#submit(payload, options = {}) ⇒ Object

Description

Executes a POST request on the resource, reload it and returns self if successful. If the response status is different from 2xx, raises a HTTP::ClientError or HTTP::ServerError.

payload

the input body of the request. It may be a serialized string, or a ruby object (that will be serialized according to the given or default content-type).

options

list of options to pass to the request (see below)

Options

:query

a hash of query parameters to pass along the request. E.g. : resource.submit(“body”, :query => => “value1”)

:headers

a hash of HTTP headers to pass along the request. E.g. : resource.submit(“body”, :headers => => ‘application/json’, :content_type => ‘application/json’)

Raises:

  • (NotImplementedError)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/restfully/resource.rb', line 127

def submit(payload, options = {})
  options = options.symbolize_keys
  raise NotImplementedError, "The POST method is not allowed for this resource." unless http_methods.include?('POST')
  raise ArgumentError, "You must pass a payload" if payload.nil?
  headers = {
    :content_type => (executed_requests['GET']['headers']['Content-Type'] || "application/x-www-form-urlencoded").split(/,/).sort{|a,b| a.length <=> b.length}[0],
    :accept => (executed_requests['GET']['headers']['Content-Type'] || "text/plain")
  }.merge(options[:headers] || {})
  options = {:headers => headers}
  options.merge!(:query => options[:query]) unless options[:query].nil?
  response = session.post(self.uri, payload, options) # raises an exception if there is an error
  stale!
  if [201, 202].include?(response.status)
    Resource.new(uri_for(response.headers['Location']), session).load
  else
    reload
  end
end

#uri_for(path) ⇒ Object



179
180
181
# File 'lib/restfully/resource.rb', line 179

def uri_for(path)
  uri.merge(URI.parse(path.to_s))
end