hyperresource_zuhrig 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 04783c914f04b3bd5312d7a58a7f59d9b171fb1c
4
+ data.tar.gz: 0efdff138134a4e51ab8b75b01a7cdadf730bddb
5
+ SHA512:
6
+ metadata.gz: c069c3a59156831c56987c5075d3bac00ecb0bb7e135d47dc2749f27fec1469226eb7b65346ce1561ed20c4f598535aeda7a8c15bd16137d41eee20098f804fc
7
+ data.tar.gz: b06dd416f2e835e7f53d921b2e644a90ea589ecec87aab44d4ef5dcd35f17b8ba9a1e0097f13f0b9a7cd58754067b62d69560d1a6d65141f27a8cca61bb16b36
@@ -0,0 +1,241 @@
1
+ require 'hyper_resource/attributes'
2
+ require 'hyper_resource/configuration'
3
+ require 'hyper_resource/exceptions'
4
+ require 'hyper_resource/link'
5
+ require 'hyper_resource/links'
6
+ require 'hyper_resource/objects'
7
+ require 'hyper_resource/version'
8
+
9
+ require 'hyper_resource/adapter'
10
+ require 'hyper_resource/adapter/hal_json'
11
+
12
+ require 'hyper_resource/modules/data_type'
13
+ require 'hyper_resource/modules/deprecations'
14
+ require 'hyper_resource/modules/http'
15
+ require 'hyper_resource/modules/config_attributes'
16
+ require 'hyper_resource/modules/internal_attributes'
17
+
18
+ require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
19
+
20
+ require 'pp'
21
+
22
+ ## HyperResource is the main resource base class. Normally it will be used
23
+ ## through subclassing, though it may also be used directly.
24
+
25
+ class HyperResource
26
+
27
+ include HyperResource::Modules::ConfigAttributes
28
+ include HyperResource::Modules::DataType
29
+ include HyperResource::Modules::Deprecations
30
+ include HyperResource::Modules::InternalAttributes
31
+ include Enumerable
32
+
33
+ private
34
+
35
+ DEFAULT_HEADERS = { 'Accept' => 'application/json' }
36
+
37
+ public
38
+
39
+ ## Create a new HyperResource, given a hash of options. These options
40
+ ## include:
41
+ ##
42
+ ## [root] The root URL of the resource.
43
+ ##
44
+ ## [auth] Authentication information. Currently only +{basic:
45
+ ## ['key', 'secret']}+ is supported.
46
+ ##
47
+ ## [namespace] Class or class name, into which resources should be
48
+ ## instantiated.
49
+ ##
50
+ ## [headers] Headers to send along with requests for this resource (as
51
+ ## well as its eventual child resources, if any).
52
+ ##
53
+ ## [faraday_options] Configuration passed to +Faraday::Connection.initialize+,
54
+ ## such as +{request: {timeout: 30}}+.
55
+ ##
56
+ def initialize(opts={})
57
+ self.root = opts[:root] if opts[:root]
58
+ self.href = opts[:href] if opts[:href]
59
+
60
+ self.hr_config = self.class.hr_config.clone
61
+
62
+ self.adapter = opts[:adapter] if opts[:adapter]
63
+ self.faraday_options = opts[:faraday_options] if opts[:faraday_options]
64
+ self.auth = opts[:auth] if opts[:auth]
65
+
66
+ self.headers = DEFAULT_HEADERS.
67
+ merge(self.class.headers || {}).
68
+ merge(opts[:headers] || {})
69
+
70
+ self.namespace = opts[:namespace] if opts[:namespace]
71
+ if !self.namespace && self.class != HyperResource
72
+ self.namespace = self.class.namespace || self.class.to_s
73
+ end
74
+
75
+ ## There's a little acrobatics in getting Attributes, Links, and Objects
76
+ ## into the correct subclass.
77
+ if self.class != HyperResource
78
+ if self.class::Attributes == HyperResource::Attributes
79
+ Object.module_eval(
80
+ "class #{self.class}::Attributes < HyperResource::Attributes; end"
81
+ )
82
+ end
83
+ if self.class::Links == HyperResource::Links
84
+ Object.module_eval(
85
+ "class #{self.class}::Links < HyperResource::Links; end"
86
+ )
87
+ end
88
+ if self.class::Objects == HyperResource::Objects
89
+ Object.module_eval(
90
+ "class #{self.class}::Objects < HyperResource::Objects; end"
91
+ )
92
+ end
93
+ end
94
+
95
+ self.attributes = self.class::Attributes.new(self)
96
+ self.links = self.class::Links.new(self)
97
+ self.objects = self.class::Objects.new(self)
98
+ self.loaded = false
99
+ end
100
+
101
+
102
+
103
+ ## Creates a new resource given args :link, :resource, :href, :response, :url,
104
+ ## and :body. Either :link or (:resource and :href and :url) are required.
105
+ # @private
106
+ def self.new_from(args)
107
+ link = args[:link]
108
+ resource = args[:resource] || link.resource
109
+ href = args[:href] || link.href
110
+ url = args[:url] || URI.join(resource.root, href || '')
111
+ response = args[:response]
112
+ body = args[:body] || {}
113
+
114
+ old_rsrc = resource
115
+ new_class = old_rsrc.get_data_type_class(:resource => old_rsrc,
116
+ :link => link,
117
+ :url => url,
118
+ :response => response,
119
+ :body => body)
120
+ new_rsrc = new_class.new(:root => old_rsrc.root,
121
+ :href => href)
122
+ new_rsrc.hr_config = old_rsrc.hr_config.clone
123
+ new_rsrc.response = response
124
+ new_rsrc.body = body
125
+ new_rsrc.adapter.apply(body, new_rsrc)
126
+ new_rsrc.loaded = true
127
+ new_rsrc
128
+ end
129
+
130
+ # @private
131
+ def new_from(args)
132
+ self.class.new_from(args)
133
+ end
134
+
135
+
136
+ ## Returns true if one or more of this object's attributes has been
137
+ ## reassigned.
138
+ def changed?(*args)
139
+ attributes.changed?(*args)
140
+ end
141
+
142
+
143
+ #### Filters
144
+
145
+ ## +incoming_body_filter+ filters a hash of attribute keys and values
146
+ ## on their way from a response body to a HyperResource. Override this
147
+ ## in a subclass of HyperResource to implement filters on incoming data.
148
+ def incoming_body_filter(attr_hash)
149
+ attr_hash
150
+ end
151
+
152
+ ## +outgoing_body_filter+ filters a hash of attribute keys and values
153
+ ## on their way from a HyperResource to a request body. Override this
154
+ ## in a subclass of HyperResource to implement filters on outgoing data.
155
+ def outgoing_body_filter(attr_hash)
156
+ attr_hash
157
+ end
158
+
159
+ ## +outgoing_uri_filter+ filters a hash of attribute keys and values
160
+ ## on their way from a HyperResource to a URL. Override this
161
+ ## in a subclass of HyperResource to implement filters on outgoing URI
162
+ ## parameters.
163
+ def outgoing_uri_filter(attr_hash)
164
+ attr_hash
165
+ end
166
+
167
+
168
+ #### Enumerable support
169
+
170
+ ## Returns the *i*th object in the first collection of objects embedded
171
+ ## in this resource. Returns nil on failure.
172
+ def [](i)
173
+ get unless loaded
174
+ self.objects.first[1][i] rescue nil
175
+ end
176
+
177
+ ## Iterates over the objects in the first collection of embedded objects
178
+ ## in this resource.
179
+ def each(&block)
180
+ get unless loaded
181
+ self.objects.first[1].each(&block) rescue nil
182
+ end
183
+
184
+ #### Magic
185
+
186
+ ## method_missing will load this resource if not yet loaded, then
187
+ ## attempt to delegate to +attributes+, then +objects+, then +links+.
188
+ ## Override with extreme care.
189
+ def method_missing(method, *args)
190
+ ## If not loaded, load and retry.
191
+ unless loaded
192
+ return self.get.send(method, *args)
193
+ end
194
+
195
+
196
+ ## Otherwise, try to match against attributes, then objects, then links.
197
+ method = method.to_s
198
+ if method[-1,1] == '='
199
+ return attributes[method[0..-2]] = args.first if attributes[method[0..-2]]
200
+ else
201
+ return attributes[method] if attributes && attributes.has_key?(method.to_s)
202
+ return objects[method] if objects && objects[method]
203
+ if links && links[method]
204
+ if args.count > 0
205
+ return links[method].where(*args)
206
+ else
207
+ return links[method]
208
+ end
209
+ end
210
+ end
211
+
212
+ raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
213
+ end
214
+
215
+ ## respond_to? is patched to return +true+ if +method_missing+ would
216
+ ## successfully delegate a method call to +attributes+, +links+, or
217
+ ## +objects+.
218
+ def respond_to?(method, *args)
219
+ return true if self.links && self.links.respond_to?(method)
220
+ return true if self.attributes && self.attributes.respond_to?(method)
221
+ return true if self.objects && self.objects.respond_to?(method)
222
+ super
223
+ end
224
+
225
+
226
+ def inspect # @private
227
+ "#<#{self.class}:0x#{"%x" % self.object_id} @root=#{self.root.inspect} "+
228
+ "@href=#{self.href.inspect} @loaded=#{self.loaded} "+
229
+ "@namespace=#{self.namespace.inspect} ...>"
230
+ end
231
+
232
+ def self.user_agent # @private
233
+ "HyperResource #{HyperResource::VERSION}"
234
+ end
235
+
236
+ def user_agent # @private
237
+ self.class.user_agent
238
+ end
239
+
240
+ end
241
+
@@ -0,0 +1,31 @@
1
+ class HyperResource
2
+
3
+ ## HyperResource::Adapter is the interface/abstract base class for
4
+ ## adapters to different hypermedia formats (e.g., HAL+JSON). New
5
+ ## adapters must implement the public methods of this class.
6
+
7
+ class Adapter
8
+ class << self
9
+
10
+ ## Serialize the given object into a string.
11
+ def serialize(object)
12
+ raise NotImplementedError, "This is an abstract method -- subclasses "+
13
+ "of HyperResource::Adapter must implement it."
14
+ end
15
+
16
+ ## Deserialize a given string into an object (Hash).
17
+ def deserialize(string)
18
+ raise NotImplementedError, "This is an abstract method -- subclasses "+
19
+ "of HyperResource::Adapter must implement it."
20
+ end
21
+
22
+ ## Use a given deserialized response object (Hash) to update a given
23
+ ## resource (HyperResource), returning the updated resource.
24
+ def apply(response, resource, opts={})
25
+ raise NotImplementedError, "This is an abstract method -- subclasses "+
26
+ "of HyperResource::Adapter must implement it."
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,92 @@
1
+ require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
2
+ require 'json'
3
+
4
+ class HyperResource
5
+ class Adapter
6
+
7
+ ## HyperResource::Adapter::HAL_JSON provides support for the HAL+JSON
8
+ ## hypermedia format by implementing the interface defined in
9
+ ## HyperResource::Adapter.
10
+
11
+ class HAL_JSON < Adapter
12
+ class << self
13
+
14
+ def serialize(object)
15
+ JSON.dump(object)
16
+ end
17
+
18
+ def deserialize(string)
19
+ JSON.parse(string)
20
+ end
21
+
22
+ def apply(response, resource, opts={})
23
+ if !response.kind_of?(Hash)
24
+ raise ArgumentError, "'response' argument must be a Hash (got #{response.inspect})"
25
+ end
26
+ if !resource.kind_of?(HyperResource)
27
+ raise ArgumentError, "'resource' argument must be a HyperResource (got #{resource.inspect})"
28
+ end
29
+
30
+ apply_objects(response, resource)
31
+ apply_links(response, resource)
32
+ apply_attributes(response, resource)
33
+ resource.loaded = true
34
+ resource.href = response['_links']['self']['href'] rescue nil
35
+ resource
36
+ end
37
+
38
+
39
+ private
40
+
41
+ def apply_objects(resp, rsrc)
42
+ return unless resp['_embedded']
43
+ objs = rsrc.objects
44
+
45
+ resp['_embedded'].each do |name, collection|
46
+ if collection.is_a? Hash
47
+ objs[name] =
48
+ rsrc.new_from(:resource => rsrc,
49
+ :body => collection,
50
+ :href => collection['_links']['self']['href'] )
51
+ else
52
+ objs[name] = collection.map do |obj|
53
+ rsrc.new_from(:resource => rsrc,
54
+ :body => obj,
55
+ :href => obj['_links']['self']['href'] )
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+
62
+ def apply_links(resp, rsrc)
63
+ return unless resp['links']
64
+ links = rsrc.links
65
+
66
+ resp['links'].each do | link |
67
+ rel = link['rel']
68
+ links[rel] = new_link_from_spec(rsrc, link)
69
+ end
70
+ end
71
+
72
+ def new_link_from_spec(resource, link_spec)
73
+ resource.class::Link.new(resource, link_spec)
74
+ end
75
+
76
+
77
+ def apply_attributes(resp, rsrc)
78
+ given_attrs = resp.reject{|k,v| %w(_links _embedded).include?(k)}
79
+ filtered_attrs = rsrc.incoming_body_filter(given_attrs)
80
+
81
+ filtered_attrs.keys.each do |attr|
82
+ rsrc.attributes[attr] = filtered_attrs[attr]
83
+ end
84
+
85
+ rsrc.attributes._hr_clear_changed
86
+ end
87
+
88
+ end
89
+ end
90
+ end
91
+ end
92
+
@@ -0,0 +1,74 @@
1
+ class HyperResource
2
+ class Attributes < Hash
3
+
4
+ attr_accessor :_resource # @private
5
+
6
+ def initialize(resource=nil)
7
+ self._resource = resource || HyperResource.new
8
+ end
9
+
10
+ ## Returns +true+ if the given attribute has been changed since creation
11
+ ## time, +false+ otherwise.
12
+ ## If no attribute is given, return whether any attributes have been
13
+ ## changed.
14
+ def changed?(attr=nil)
15
+ @_hr_changed ||= Hash.new(false)
16
+ return @_hr_changed[attr.to_sym] if attr
17
+ return @_hr_changed.keys.count > 0
18
+ end
19
+
20
+ ## Returns a hash of the attributes and values which have been changed
21
+ ## since creation time.
22
+ def changed_attributes
23
+ @_hr_changed.select{|k,v| v}.keys.inject({}) {|h,k| h[k]=self[k]; h}
24
+ end
25
+
26
+ # @private
27
+ def []=(attr, value)
28
+ return self[attr] if self.has_key?(attr.to_s) && self[attr] == value
29
+ _hr_mark_changed(attr)
30
+ super(attr.to_s, value)
31
+ end
32
+
33
+ # @private
34
+ def [](key)
35
+ return super(key.to_s) if self.has_key?(key.to_s)
36
+ return super(key.to_sym) if self.has_key?(key.to_sym)
37
+ nil
38
+ end
39
+
40
+ # @private
41
+ def method_missing(method, *args)
42
+ method = method.to_s
43
+ if self.has_key?(method)
44
+ self[method]
45
+ elsif method[-1,1] == '='
46
+ self[method[0..-2]] = args.first
47
+ else
48
+ raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
49
+ end
50
+ end
51
+
52
+ # @private
53
+ def respond_to?(method, *args)
54
+ method = method.to_s
55
+ return true if self.has_key?(method)
56
+ return true if method[-1,1] == '=' && self.has_key?(method[0..-2])
57
+ super
58
+ end
59
+
60
+ # @private
61
+ def _hr_clear_changed # @private
62
+ @_hr_changed = nil
63
+ end
64
+
65
+ # @private
66
+ def _hr_mark_changed(attr, is_changed=true)
67
+ attr = attr.to_sym
68
+ @_hr_changed ||= Hash.new(false)
69
+ @_hr_changed[attr] = is_changed
70
+ end
71
+
72
+ end
73
+ end
74
+