hyperresource_zuhrig 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+