rails3-opensocial 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module OpenSocial #:nodoc:
17
+
18
+ # Acts as a wrapper for an OpenSocial group.
19
+ #
20
+ # The Group class takes input JSON as an initialization parameter, and
21
+ # iterates through each of the key/value pairs of that JSON. For each key
22
+ # that is found, an attr_accessor is constructed, allowing direct access
23
+ # to the value. Each value is stored in the attr_accessor, either as a
24
+ # String, Fixnum, Hash, or Array.
25
+ #
26
+
27
+
28
+ class Group < Base
29
+
30
+ # Initializes the Group based on the provided json fragment. If no JSON
31
+ # is provided, an empty object (with no attributes) is created.
32
+ def initialize(json)
33
+ if json
34
+ json.each do |key, value|
35
+ proper_key = key.snake_case
36
+ begin
37
+ self.send("#{proper_key}=", value)
38
+ rescue NoMethodError
39
+ add_attr(proper_key)
40
+ self.send("#{proper_key}=", value)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Provides the ability to request a Collection of groups for a given
48
+ # user.
49
+ #
50
+ # The FetchGroupsRequest wraps a simple request to an OpenSocial
51
+ # endpoint for a collection of groups. As parameters, it accepts
52
+ # a user ID. This request may be used, standalone, by calling send, or
53
+ # bundled into an RpcRequest.
54
+ #
55
+
56
+
57
+ class FetchGroupsRequest < Request
58
+ # Defines the service fragment for use in constructing the request URL or
59
+ # JSON
60
+ SERVICE = 'groups'
61
+
62
+ # Initializes a request to fetch groups for the specified user, or the
63
+ # default (@me). A valid Connection is not necessary if the request is to
64
+ # be used as part of an RpcRequest.
65
+ def initialize(connection = nil, guid = '@me')
66
+ super
67
+ end
68
+
69
+ # Sends the request, passing in the appropriate SERVICE and specified
70
+ # instance variables.
71
+ def send
72
+ json = send_request(SERVICE, @guid)
73
+
74
+ groups = Collection.new
75
+ for entry in json['entry']
76
+ group = Group.new(entry)
77
+ groups[group.id] = group
78
+ end
79
+
80
+ return groups
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,201 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Extends the OpenSocial module, providing a wrapper for OpenSocial people.
16
+ #
17
+ # Person: Acts as a wrapper for an OpenSocial person.
18
+ # FetchPersonRequest: Provides the ability to request a single person.
19
+ # FetchPeopleRequest: Provides the ability to request a collection of people
20
+ # by describing their relationship to a single person.
21
+ #
22
+
23
+
24
+ module OpenSocial #:nodoc:
25
+
26
+ # Acts as a wrapper for an OpenSocial person.
27
+ #
28
+ # The Person class takes input JSON as an initialization parameter, and
29
+ # iterates through each of the key/value pairs of that JSON. For each key
30
+ # that is found, an attr_accessor is constructed, allowing direct access
31
+ # to the value. Each value is stored in the attr_accessor, either as a
32
+ # String, Fixnum, Hash, or Array.
33
+ #
34
+
35
+
36
+ class Person < Base
37
+
38
+ # Initializes the Person based on the provided json fragment. If no JSON
39
+ # is provided, an empty object (with no attributes) is created.
40
+ def initialize(json)
41
+ if json
42
+ json.each do |key, value|
43
+ proper_key = key.snake_case
44
+ begin
45
+ self.send("#{proper_key}=", value)
46
+ rescue NoMethodError
47
+ add_attr(proper_key)
48
+ self.send("#{proper_key}=", value)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Returns the complete name of the Person, to the best ability, given
55
+ # available fields.
56
+ def long_name
57
+ if @name && @name['givenName'] && @name['familyName']
58
+ return @name['givenName'] + ' ' + @name['familyName']
59
+ elsif @nickname
60
+ return @nickname
61
+ else
62
+ return ''
63
+ end
64
+ end
65
+
66
+ # Returns the first name or nickname of the Person, to the best ability,
67
+ # given available fields.
68
+ def short_name
69
+ if @name && @name['givenName']
70
+ return @name['givenName']
71
+ elsif @nickname
72
+ return @nickname
73
+ else
74
+ return ''
75
+ end
76
+ end
77
+ end
78
+
79
+ # Provides the ability to request a single Person.
80
+ #
81
+ # The FetchPeopleRequests wraps a simple request to an OpenSocial
82
+ # endpoint for a single person. As parameters, it accepts a user ID and
83
+ # selector and optionally an ID of a particular person, in order to display
84
+ # the user ID's view of that person. This request may be used, standalone,
85
+ # by calling send, or bundled into an RpcRequest.
86
+ #
87
+
88
+
89
+ class FetchPersonRequest < Request
90
+ # Defines the service fragment for use in constructing the request URL or
91
+ # JSON
92
+ SERVICE = 'people'
93
+ DEFAULT_FIELDS = [:id, :dateOfBirth, :name, :emails, :gender, :state, :postalCode, :ethnicity, :relationshipStatus, :thumbnailUrl, :displayName]
94
+
95
+ # Initializes a request to the specified user, or the default (@me, @self).
96
+ # A valid Connection is not necessary if the request is to be used as part
97
+ # of an RpcRequest.
98
+ def initialize(connection = nil, guid = '@me', selector = '@self')
99
+ super
100
+ end
101
+
102
+ # Sends the request, passing in the appropriate SERVICE and specified
103
+ # instance variables.
104
+ def send
105
+ json = send_request(SERVICE, @guid, @selector, nil, false, :fields => DEFAULT_FIELDS.join(","))
106
+
107
+ return parse_response(json['entry'])
108
+ end
109
+
110
+ # Selects the appropriate fragment from the JSON response in order to
111
+ # create a native object.
112
+ def parse_rpc_response(response)
113
+ return parse_response(response['data'])
114
+ end
115
+
116
+ # Converts the request into a JSON fragment that can be used as part of a
117
+ # larger RpcRequest.
118
+ def to_json(*a)
119
+ value = {
120
+ 'method' => SERVICE + GET,
121
+ 'params' => {
122
+ 'userId' => ["#{@guid}"],
123
+ 'groupId' => "#{@selector}"
124
+ },
125
+ 'id' => @key
126
+ }.to_json(*a)
127
+ end
128
+
129
+ private
130
+
131
+ # Converts the JSON response into a person.
132
+ def parse_response(response)
133
+ return Person.new(response)
134
+ end
135
+ end
136
+
137
+ # Provides the ability to request a Collection of people by describing their
138
+ # relationship to a single person.
139
+ #
140
+ # The FetchPeopleRequests wraps a simple request to an OpenSocial
141
+ # endpoint for a Collection of people. As parameters, it accepts
142
+ # a user ID and selector. This request may be used, standalone, by calling
143
+ # send, or bundled into an RpcRequest.
144
+ #
145
+
146
+
147
+ class FetchPeopleRequest < Request
148
+ # Defines the service fragment for use in constructing the request URL or
149
+ # JSON
150
+ SERVICE = 'people'
151
+
152
+ # Initializes a request to the specified user's group, or the default (@me,
153
+ # @friends). A valid Connection is not necessary if the request is to be
154
+ # used as part of an RpcRequest.
155
+ def initialize(connection = nil, guid = '@me', selector = '@friends', extra_fields = {})
156
+ super
157
+ @extra_fields = extra_fields
158
+ end
159
+
160
+ # Sends the request, passing in the appropriate SERVICE and specified
161
+ # instance variables.
162
+ def send
163
+ @extra_fields[:fields] ||= FetchPersonRequest::DEFAULT_FIELDS.join(",")
164
+ json = send_request(SERVICE, @guid, @selector, nil, false, @extra_fields)
165
+
166
+ return parse_response(json['entry'])
167
+ end
168
+
169
+ # Selects the appropriate fragment from the JSON response in order to
170
+ # create a native object.
171
+ def parse_rpc_response(response)
172
+ return parse_response(response['data']['list'])
173
+ end
174
+
175
+ # Converts the request into a JSON fragment that can be used as part of a
176
+ # larger RPC request.
177
+ def to_json(*a)
178
+ value = {
179
+ 'method' => SERVICE + GET,
180
+ 'params' => {
181
+ 'userId' => ["#{@guid}"],
182
+ 'groupId' => "#{@selector}"
183
+ },
184
+ 'id' => @key
185
+ }.to_json(*a)
186
+ end
187
+
188
+ private
189
+
190
+ # Converts the JSON response into a Collection of people, indexed by id.
191
+ def parse_response(response)
192
+ people = Collection.new
193
+ for entry in response
194
+ person = Person.new(entry)
195
+ people[person.id] = person
196
+ end
197
+
198
+ return people
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,258 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module OpenSocial #:nodoc:
16
+
17
+ # Provides a wrapper for a single request to an OpenSocial endpoint, either
18
+ # as a standalone request, or as a fragment of an RPC request.
19
+ #
20
+ # The Request class wraps an HTTP request to an OpenSocial endpoint for
21
+ # social data. A Request may be used, directly, to access social resources
22
+ # that may not be currently wrapped by Request's child classes. Used in this
23
+ # was it gives near-raw access to social data. The Request class supplies
24
+ # OAuth signing when the supplied connection contains appropriate
25
+ # credentials.
26
+ #
27
+
28
+
29
+ class Request
30
+ GET = '.get'
31
+
32
+ # Defines the connection that will be used in the request.
33
+ attr_accessor :connection
34
+
35
+ # Defines the guid for the request.
36
+ attr_accessor :guid
37
+
38
+ # Defines the selector for the request.
39
+ attr_accessor :selector
40
+
41
+ # Defines the pid for the request.
42
+ attr_accessor :pid
43
+
44
+ # Defines the key used to lookup the request result in an RPC request.
45
+ attr_accessor :key
46
+
47
+ # Initializes a request using the optionally supplied connection, guid,
48
+ # selector, and pid.
49
+ def initialize(connection = nil, guid = nil, selector = nil, pid = nil)
50
+ @connection = connection
51
+ @guid = guid
52
+ @selector = selector
53
+ @pid = pid
54
+ end
55
+
56
+ # Generates a request given the service, guid, selector, and pid, to the
57
+ # OpenSocial endpoint by constructing the service URI and dispatching the
58
+ # request. When data is returned, it is parsed as JSON after being
59
+ # optionally unescaped.
60
+ def send_request(service, guid, selector = nil, pid = nil,
61
+ unescape = false, extra_fields = {})
62
+ if !@connection
63
+ raise RequestException.new('Request requires a valid connection.')
64
+ end
65
+
66
+ uri = @connection.service_uri(@connection.container[:rest] + service,
67
+ guid, selector, pid, extra_fields)
68
+ data = dispatch(uri)
69
+
70
+ if unescape
71
+ JSON.parse(data.os_unescape)
72
+ else
73
+ JSON.parse(data)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Dispatches a request to a given URI with optional POST data. If a
80
+ # request's connection has specified HMAC-SHA1 authentication, OAuth
81
+ # parameters and signature are appended to the request.
82
+ def dispatch(uri, post_data = nil)
83
+ http = Net::HTTP.new(uri.host, uri.port)
84
+
85
+ if post_data
86
+ req = Net::HTTP::Post.new(uri.request_uri)
87
+ req.set_form_data(post_data)
88
+ else
89
+ req = Net::HTTP::Get.new(uri.request_uri)
90
+ end
91
+
92
+ @connection.sign!(http, req)
93
+
94
+ if post_data
95
+ resp = http.post(req.path, post_data)
96
+ check_for_json_error!(resp)
97
+ else
98
+ resp = http.get(req.path)
99
+ check_for_http_error!(resp, uri) # TODO uri for debugging only
100
+ end
101
+
102
+ return resp.body
103
+ end
104
+
105
+ # Checks the response object's status code. If the response is is
106
+ # unauthorized, an exception is raised.
107
+ def check_for_http_error!(resp, req_uri=nil) # TODO uri for debugging only
108
+ if !resp.kind_of?(Net::HTTPSuccess)
109
+ if resp.is_a?(Net::HTTPUnauthorized)
110
+ if Object.const_defined?(:HoptoadNotifier)
111
+ HoptoadNotifier.notify(
112
+ :error_class => "Myspace API Auth error",
113
+ :error_message => "Myspace API Auth error",
114
+ :request => {:params => {"body" => resp.body,
115
+ "uri" => req_uri.to_s}})
116
+ end
117
+ raise AuthException.new('The request lacked proper authentication ' +
118
+ 'credentials to retrieve data.')
119
+ else
120
+ resp.value
121
+ end
122
+ end
123
+ end
124
+
125
+ # Checks the JSON response for a status code. If a code is present an
126
+ # exception is raised.
127
+ def check_for_json_error!(resp)
128
+ json = JSON.parse(resp.body)
129
+ if json.is_a?(Hash) && json.has_key?('code') && json.has_key?('message')
130
+ rc = json['code']
131
+ message = json['message']
132
+ case rc
133
+ when 401:
134
+ raise AuthException.new('The request lacked proper authentication ' +
135
+ 'credentials to retrieve data.')
136
+ else
137
+ raise RequestException.new("The request returned an unsupported " +
138
+ "status code: #{rc} #{message}.")
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ # Provides a wrapper for a single RPC request to an OpenSocial endpoint,
145
+ # composed of one or more individual requests.
146
+ #
147
+ # The RpcRequest class wraps an HTTP request to an OpenSocial endpoint for
148
+ # social data. An RpcRequest is intended to be used as a container for one
149
+ # or more Requests (or Fetch*Requests), but may also be used with a manually
150
+ # constructed post body. The RpcRequest class uses OAuth signing inherited
151
+ # from the Request class, when appropriate OAuth credentials are supplied.
152
+ #
153
+
154
+
155
+ class RpcRequest < Request
156
+
157
+ # Defines the requests sent in the single RpcRequest. The requests are
158
+ # stored a key/value pairs.
159
+ attr_accessor :requests
160
+
161
+ # Initializes an RpcRequest with the supplied connection and an optional
162
+ # hash of requests.
163
+ def initialize(connection, requests = {})
164
+ @connection = connection
165
+
166
+ @requests = requests
167
+ end
168
+
169
+ # Adds one or more requests to the RpcRequest. Expects a hash of key/value
170
+ # pairs (key used to refernece the data when it returns => the Request).
171
+ def add(requests = {})
172
+ @requests.merge!(requests)
173
+ end
174
+
175
+ # Sends an RpcRequest to the OpenSocial endpoint by constructing JSON for
176
+ # the POST body and delegating the request to send_request. If an
177
+ # RpcRequest is sent with an empty list of requests, an exception is
178
+ # thrown. The response JSON is optionally unescaped (defaulting to true).
179
+ def send(unescape = true)
180
+ if @requests.length == 0
181
+ raise RequestException.new('RPC request requires a non-empty hash ' +
182
+ 'of requests in order to be sent.')
183
+ end
184
+
185
+ json = send_request(request_json, unescape)
186
+ end
187
+
188
+ # Sends an RpcRequest to the OpenSocial endpoint by constructing the
189
+ # service URI and dispatching the request. This method is public so that
190
+ # an arbitrary POST body can be constructed and sent. The response JSON is
191
+ # optionally unescaped.
192
+ def send_request(post_data, unescape)
193
+ uri = @connection.service_uri(@connection.container[:rpc], nil, nil, nil)
194
+ data = dispatch(uri, post_data)
195
+
196
+ parse_response(data, unescape)
197
+ end
198
+
199
+ private
200
+
201
+ # Parses the response JSON. First, the JSON is unescaped, when specified,
202
+ # then for each element specified in @requests, the appropriate response
203
+ # is selected from the larger JSON response. This element is then delegated
204
+ # to the appropriate class to be turned into a native object (Person,
205
+ # Activity, etc.)
206
+ def parse_response(response, unescape)
207
+ if unescape
208
+ parsed = JSON.parse(response.os_unescape)
209
+ else
210
+ parsed = JSON.parse(response)
211
+ end
212
+ keyed_by_id = key_by_id(parsed)
213
+
214
+ native_objects = {}
215
+ @requests.each_pair do |key, request|
216
+ native_object = request.parse_rpc_response(keyed_by_id[key.to_s])
217
+ native_objects.merge!({key => native_object})
218
+ end
219
+
220
+ return native_objects
221
+ end
222
+
223
+ # Constructs a hash of the elements in data referencing each element
224
+ # by its 'id' attribute.
225
+ def key_by_id(data)
226
+ keyed_by_id = {}
227
+ for entry in data
228
+ keyed_by_id.merge!({entry['id'] => entry})
229
+ end
230
+
231
+ return keyed_by_id
232
+ end
233
+
234
+ # Modifies each request in an outgoing RpcRequest so that its key is set
235
+ # to the value specified when added to the RpcRequest.
236
+ def request_json
237
+ keyed_requests = []
238
+ @requests.each_pair do |key, request|
239
+ request.key = key
240
+ keyed_requests << request
241
+ end
242
+
243
+ return keyed_requests.to_json
244
+ end
245
+ end
246
+
247
+ # An exception thrown when a request cannot return data.
248
+ #
249
+
250
+
251
+ class RequestException < RuntimeError; end
252
+
253
+ # An exception thrown when a request returns a 401 unauthorized status.
254
+ #
255
+
256
+
257
+ class AuthException < RuntimeError; end
258
+ end