Class: HyperResource

Inherits:
Object
  • Object
show all
Includes:
Modules::HTTP, Modules::Utils
Defined in:
lib/hyper_resource.rb,
lib/hyper_resource/links.rb,
lib/hyper_resource/adapter.rb,
lib/hyper_resource/objects.rb,
lib/hyper_resource/version.rb,
lib/hyper_resource/attributes.rb,
lib/hyper_resource/adapter/hal_json.rb

Overview

TODO: incoming_filter, outgoing_filter as_json, to_json (in adapter?) save, update, create, delete

Defined Under Namespace

Modules: Modules Classes: Adapter, Attributes, ClientError, Exception, Link, Links, Objects, Response, ResponseError, ServerError

Constant Summary collapse

DEFAULT_HEADERS =

:nodoc:

{
  'Accept' => 'application/json'
}
VERSION =
'0.1.9'
VERSION_DATE =
'2013-09-27'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ HyperResource

Create a new HyperResource, given a hash of options. These options include:

[root] The root URL of the resource.

[auth] Authentication information. Currently only {basic: ['key', 'secret']} is supported.

[namespace] Class or class name, into which resources should be instantiated.

[headers] Headers to send along with requests for this resource (as well as its eventual child resources, if any).



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
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/hyper_resource.rb', line 81

def initialize(opts={})
  return init_from_resource(opts) if opts.kind_of?(HyperResource)

  self.root       = opts[:root] || self.class.root
  self.href       = opts[:href] || ''
  self.auth       = (self.class.auth || {}).merge(opts[:auth] || {})
  self.namespace  = opts[:namespace] || self.class.namespace
  self.headers    = DEFAULT_HEADERS.merge(self.class.headers || {}).
                                    merge(opts[:headers]     || {})

  ## There's a little acrobatics in getting Attributes, Links, and Objects
  ## into the correct subclass.
  if self.class != HyperResource
    if self.class::Attributes == HyperResource::Attributes
      Object.module_eval(
        "class #{self.class}::Attributes < HyperResource::Attributes; end"
      )
    end
    if self.class::Links == HyperResource::Links
      Object.module_eval(
        "class #{self.class}::Links < HyperResource::Links; end"
      )
    end
    if self.class::Objects == HyperResource::Objects
      Object.module_eval(
        "class #{self.class}::Objects < HyperResource::Objects; end"
      )
    end
  end

  self.attributes = self.class::Attributes.new(self)
  self.links      = self.class::Links.new(self)
  self.objects    = self.class::Objects.new(self)

  self.loaded     = false

  self.adapter    = opts[:adapter] || self.class.adapter ||
                    HyperResource::Adapter::HAL_JSON
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

method_missing will load this resource if not yet loaded, then attempt to delegate to attributes, then objects, then links.

Raises:

  • (NoMethodError)


236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/hyper_resource.rb', line 236

def method_missing(method, *args)
  self.get unless self.loaded

  method = method.to_s
  if method[-1] == '='
    return attributes[method[0..-2]] = args.first if attributes[method[0..-2]]
  else
    return attributes[method] if attributes && attributes[method]
    return objects[method] if objects && objects[method]
    return links[method] if links && links[method]
  end

  raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
end

Class Method Details

._hr_attributes ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/hyper_resource.rb', line 37

def self._hr_attributes
  [ :root,
    :href,
    :auth,
    :headers,
    :namespace,
    :adapter,

    :request,
    :response,
    :response_object,

    :attributes,
    :links,
    :objects,

    :loaded
  ]
end

._hr_class_attributes ⇒ Object



28
29
30
31
32
33
34
35
# File 'lib/hyper_resource.rb', line 28

def self._hr_class_attributes
  [ :root,             ## e.g. 'https://example.com/api/v1'
    :auth,             ## e.g. {:basic => ['username', 'password']}
    :headers,          ## e.g. {'Accept' => 'application/vnd.example+json'}
    :namespace,        ## e.g. 'ExampleAPI', or the class ExampleAPI itself
    :adapter           ## subclass of HR::Adapter
  ]
end

.get_response_class(response, namespace) ⇒ Object

Returns the class into which the given response should be cast. If the object is not loaded yet, or if opts[:namespace] is not set, returns self.

Otherwise, get_response_class uses get_response_data_type to determine subclass name, glues it to the given namespace, and creates the class if it's not there yet. E.g., given a namespace of FooAPI and a response content-type of "application/vnd.foocorp.fooapi.v1+json;type=User", this should return FooAPI::User (even if FooAPI::User hadn't existed yet).



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/hyper_resource.rb', line 175

def self.get_response_class(response, namespace)
  if self.to_s == 'HyperResource'
    return self unless namespace
  end

  namespace ||= self.to_s

  type_name = self.get_response_data_type(response)
  return self unless type_name

  class_name = "#{namespace}::#{type_name}"
  class_name.gsub!(/[^_0-9A-Za-z:]/, '')  ## sanitize class_name

  ## Return data type class if it exists
  klass = eval(class_name) rescue :sorry_dude
  return klass if klass.is_a?(Class)

  ## Data type class didn't exist -- create namespace (if necessary),
  ## then the data type class
  if namespace != ''
    nsc = eval(namespace) rescue :bzzzzzt
    unless nsc.is_a?(Class)
      Object.module_eval "class #{namespace} < #{self}; end"
    end
  end
  Object.module_eval "class #{class_name} < #{namespace}; end"
  eval(class_name)
end

.get_response_data_type(response) ⇒ Object

Inspects the given response, and returns a string describing this resource's data type.

By default, this method looks for a type=... modifier in the response's Content-type and returns that value, capitalized.

Override this method in a subclass to alter HyperResource's behavior.



217
218
219
220
221
222
# File 'lib/hyper_resource.rb', line 217

def self.get_response_data_type(response)
  return nil unless response
  return nil unless content_type = response['content-type']
  return nil unless m=content_type.match(/;\s* type=(?<type> [0-9A-Za-z:]+)/x)
  m[:type][0].upcase + m[:type][1..-1]
end

Instance Method Details

#[](i) ⇒ Object

Returns the ith object in the first collection of objects embedded in this resource. Equivalent to self.objects[i].



232
# File 'lib/hyper_resource.rb', line 232

def [](i); self.objects.ith(i) end

Returns a new HyperResource based on the given link href.



137
138
139
140
141
142
143
# File 'lib/hyper_resource.rb', line 137

def _new_from_link(href)
  self.class.new(:root    => self.root,
                 :auth    => self.auth,
                 :headers => self.headers,
                 :namespace => self.namespace,
                 :href    => href)
end

#changed?(*args) ⇒ Boolean

Returns:

  • (Boolean)


132
133
134
# File 'lib/hyper_resource.rb', line 132

def changed?(*args)
  attributes.changed?(*args)
end

#first ⇒ Object

Returns the first object in the first collection of objects embedded in this resource. Equivalent to self.objects.first.



228
# File 'lib/hyper_resource.rb', line 228

def first; self.objects.first end

#get_response_class ⇒ Object



159
160
161
162
# File 'lib/hyper_resource.rb', line 159

def get_response_class
  self.namespace ||= self.class.to_s unless self.class.to_s=='HyperResource'
  self.class.get_response_class(self.response, self.namespace)
end

#get_response_data_type ⇒ Object



205
206
207
# File 'lib/hyper_resource.rb', line 205

def get_response_data_type
  self.class.get_response_data_type(self.response)
end

#incoming_filter(attr_hash) ⇒ Object



151
152
153
# File 'lib/hyper_resource.rb', line 151

def incoming_filter(attr_hash)
  attr_hash
end

#inspect ⇒ Object

:nodoc:



252
253
254
255
256
# File 'lib/hyper_resource.rb', line 252

def inspect # :nodoc:
  "#<#{self.class}:0x#{"%x" % self.object_id} @root=#{self.root.inspect} "+
  "@href=#{self.href.inspect} @loaded=#{self.loaded} "+
  "@namespace=#{self.namespace.inspect} ...>"
end

#outgoing_filter(attr_hash) ⇒ Object



155
156
157
# File 'lib/hyper_resource.rb', line 155

def outgoing_filter(attr_hash)
  attr_hash
end

#response_body ⇒ Object

response_body is deprecated in favor of response_object.



259
# File 'lib/hyper_resource.rb', line 259

def response_body; response_object end

#to_response_class ⇒ Object



145
146
147
148
149
# File 'lib/hyper_resource.rb', line 145

def to_response_class
  response_class = self.get_response_class
  return self if self.class == response_class
  response_class.new(self)
end