rails3-opensocial 0.0.5

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.
@@ -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