rest-graph 1.4.6 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGES +67 -0
  2. data/CONTRIBUTORS +1 -0
  3. data/Gemfile +9 -1
  4. data/Gemfile.lock +13 -3
  5. data/README +10 -3
  6. data/README.rdoc +10 -3
  7. data/Rakefile +5 -2
  8. data/example/{rails → rails2}/README +0 -0
  9. data/example/{rails → rails2}/Rakefile +0 -0
  10. data/example/{rails → rails2}/app/controllers/application_controller.rb +4 -0
  11. data/example/{rails → rails2}/config/boot.rb +6 -2
  12. data/example/{rails → rails2}/config/environment.rb +2 -2
  13. data/example/{rails → rails2}/config/environments/development.rb +0 -0
  14. data/example/{rails → rails2}/config/environments/production.rb +0 -0
  15. data/example/{rails → rails2}/config/environments/test.rb +0 -0
  16. data/example/{rails → rails2}/config/initializers/cookie_verification_secret.rb +0 -0
  17. data/example/{rails → rails2}/config/initializers/new_rails_defaults.rb +0 -0
  18. data/example/{rails → rails2}/config/initializers/session_store.rb +1 -1
  19. data/example/{rails → rails2}/config/rest-graph.yaml +0 -0
  20. data/example/{rails → rails2}/config/routes.rb +0 -0
  21. data/example/{rails → rails2}/log +0 -0
  22. data/example/{rails → rails2}/script/console +0 -0
  23. data/example/{rails → rails2}/script/server +0 -0
  24. data/example/{rails → rails2}/test/functional/application_controller_test.rb +10 -1
  25. data/example/{rails → rails2}/test/test_helper.rb +0 -0
  26. data/example/{rails → rails2}/test/unit/rails_util_test.rb +0 -0
  27. data/lib/rest-graph.rb +150 -49
  28. data/lib/rest-graph/rails_util.rb +31 -27
  29. data/lib/rest-graph/version.rb +1 -1
  30. data/rest-graph.gemspec +16 -13
  31. data/test/test_api.rb +1 -1
  32. data/test/test_error.rb +51 -0
  33. data/test/test_handler.rb +4 -2
  34. data/test/test_misc.rb +29 -0
  35. data/test/test_old.rb +33 -0
  36. data/test/test_page.rb +54 -0
  37. data/test/test_parse.rb +7 -0
  38. data/test/test_rest-graph.rb +10 -0
  39. metadata +69 -50
  40. data/test/test_access_token.rb +0 -26
data/CHANGES CHANGED
@@ -1,5 +1,72 @@
1
1
  = rest-graph changes history
2
2
 
3
+ == rest-graph 1.5.0 -- 2010-10-11
4
+
5
+ * [RestGraph] Make sure RestGraph::Error#message is string, that way,
6
+ irb could print out error message correctly. Introduced
7
+ RestGraph::Error#error for original error hash. Thanks Bluce.
8
+
9
+ * [RestGraph] Make RestGraph#inspect honor default attributes, see:
10
+ http://groups.google.com/group/rest-graph/browse_thread/thread/7ad5c81fbb0334e8
11
+
12
+ * [RestGraph] Introduced RestGraph::Error::AccessToken,
13
+ RestGraph::Error::InvalidAccessToken,
14
+ RestGraph::Error::MissingAccessToken.
15
+ RestGraph::Error::AccessToken is the parent of the others,
16
+ and RestGraph::Error is the parent of all above.
17
+
18
+ * [RestGraph] Add RestGraph#next_page and RestGraph#prev_page.
19
+ To get next page for a result from Facebook, example:
20
+
21
+ rg.next_page(rg.get('me/feed'))
22
+
23
+ * [RestGraph] Add RestGraph#for_pages that would crawl from page 1 to
24
+ a number of pages you specified. For example, this might
25
+ crawl down all the feeds:
26
+
27
+ rg.for_pages(rg.get('me/feed'), 1000)
28
+
29
+ * [RestGraph] Added RestGraph#secret_old_rest, see:
30
+ http://www.nivas.hr/blog/2010/09/03/facebook-php-sdk-access-token-signing-bug/
31
+
32
+ If you're getting this error from calling old_rest:
33
+
34
+ The method you are calling or the FQL table you are querying cannot be
35
+ called using a session secret or by a desktop application.
36
+
37
+ Then try secret_old_rest instead. The problem is that the access_token
38
+ should be formatted by "#{app_id}|#{secret}" instead of the usual one.
39
+
40
+ * [RestGraph] Added RestGraph#strict.
41
+ In some API, e.g. admin.getAppProperties, Facebook returns
42
+ broken JSON, which is double encoded, and is not a well-formed
43
+ JSON. That case, we'll need to double parse the JSON to get
44
+ the correct result. For example, Facebook might return this:
45
+
46
+ "{\"app_id\":\"123\"}"
47
+
48
+ instead of the correct one:
49
+
50
+ {"app_id":"123"}
51
+
52
+ P.S. This doesn't matter for people who don't use :auto_decode.
53
+ So under non-strict mode, which is the default, rest-graph
54
+ would handle this for you.
55
+
56
+ * [RestGraph] Fallback to ruby-hmac gem if we have an old openssl lib when
57
+ parsing signed_request which requires HMAC SHA256.
58
+ (e.g. Mac OS 10.5) Thanks Barnabas Debreczeni!
59
+
60
+ * [RailsUtil] Only rescue RestGraph::Error::AccessToken in controller,
61
+ make other errors raise through.
62
+
63
+ * [RailsUtil] Remove bad fbs in cookies when doing new authorization.
64
+ * [RailsUtil] Make signed_request and session higher priority than
65
+ fbs in cookies. Since Facebook is not deleting bad fbs,
66
+ I guess? Thanks Barnabas Debreczeni!
67
+
68
+ * [RailsUtil] URI.encode before URI.parse for broken URL Facebook passed.
69
+
3
70
  == rest-graph 1.4.6 -- 2010-09-01
4
71
 
5
72
  * [RestGraph] Now it will try to pick yajl-ruby or json gem from memory first,
@@ -2,6 +2,7 @@ Lin Jen-Shin (godfat)
2
2
  Jaime Cham (jcham)
3
3
  Andrew Liu (eggegg)
4
4
  John Fan (johnfan)
5
+ Barnabas Debreczeni (keo)
5
6
  Florent Vaucelle (florent)
6
7
  Ethan Czahor (ethanz5)
7
8
  Nicolas Fouché (nfo)
data/Gemfile CHANGED
@@ -4,11 +4,19 @@ source 'http://rubygems.org'
4
4
  gem 'rest-client'
5
5
 
6
6
  group :test do
7
+ # optional
8
+ gem 'rack'
9
+
10
+ # optional, pick one json backend
7
11
  gem 'yajl-ruby'
8
12
  gem 'json'
9
13
  gem 'json_pure'
10
14
 
11
- gem 'rack'
15
+ # falls back from openssl
16
+ gem 'ruby-hmac'
17
+
18
+ # for testing
19
+ gem 'bones'
12
20
  gem 'rr'
13
21
  gem 'webmock'
14
22
  gem 'bacon'
@@ -1,30 +1,40 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- addressable (2.2.0)
4
+ addressable (2.2.1)
5
5
  bacon (1.1.0)
6
+ bones (3.4.7)
7
+ little-plugger (>= 1.1.2)
8
+ loquacious (>= 1.6.4)
9
+ rake (>= 0.8.7)
6
10
  crack (0.1.8)
7
11
  json (1.4.6)
8
12
  json_pure (1.4.6)
13
+ little-plugger (1.1.2)
14
+ loquacious (1.7.0)
9
15
  mime-types (1.16)
10
16
  rack (1.2.1)
17
+ rake (0.8.7)
11
18
  rest-client (1.6.1)
12
19
  mime-types (>= 1.16)
13
20
  rr (1.0.0)
14
- webmock (1.3.4)
21
+ ruby-hmac (0.4.0)
22
+ webmock (1.3.5)
15
23
  addressable (>= 2.1.1)
16
24
  crack (>= 0.1.7)
17
- yajl-ruby (0.7.7)
25
+ yajl-ruby (0.7.8)
18
26
 
19
27
  PLATFORMS
20
28
  ruby
21
29
 
22
30
  DEPENDENCIES
23
31
  bacon
32
+ bones
24
33
  json
25
34
  json_pure
26
35
  rack
27
36
  rest-client
28
37
  rr
38
+ ruby-hmac
29
39
  webmock
30
40
  yajl-ruby
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = rest-graph 1.4.6
1
+ = rest-graph 1.5.0
2
2
  by Cardinal Blue ( http://cardinalblue.com )
3
3
 
4
4
  == LINKS:
@@ -20,8 +20,9 @@ A super simple Facebook Open Graph API client
20
20
 
21
21
  == REQUIREMENTS:
22
22
 
23
- * Tested with MRI 1.8.7 and 1.9.2 and Rubinius HEAD
23
+ * Tested with MRI 1.8.7 and 1.9.2 and Rubinius 1.1.0
24
24
  * gem install rest-client
25
+ * gem install yajl-ruby (optional)
25
26
  * gem install json (optional)
26
27
  * gem install json_pure (optional)
27
28
  * gem install rack (optional, to parse access_token in HTTP_COOKIE)
@@ -97,6 +98,7 @@ Here are ALL the available options for new instance of RestGraph.
97
98
 
98
99
  rg = RestGraph.new(
99
100
  :access_token => TOKEN , # default nil
101
+ :strict => false , # this is the default
100
102
  :graph_server => 'https://graph.facebook.com/', # this is the default
101
103
  :old_server => 'https://api.facebook.com/' , # this is the default
102
104
  :accept => 'text/javascript' , # this is the default
@@ -159,7 +161,7 @@ options for RestGraph instance are also valid options for rest_graph_setup.
159
161
  # site or iframe canvas application, you might want
160
162
  # to just use the Rails (or other framework) session.
161
163
 
162
- === Setup procedures:
164
+ === Alternate ways to setup RestGraph:
163
165
 
164
166
  1. Set upon RestGraph object creation:
165
167
 
@@ -239,6 +241,11 @@ Call functionality from Facebook's old REST API:
239
241
  # if Facebook is not returning a proper JSON
240
242
  # response. Otherwise, this could be omitted.
241
243
 
244
+ # Some Old Rest API requires a special access token with app secret
245
+ # inside of it. For those methods, use secret_old_rest instead of the
246
+ # usual old_rest with common access token.
247
+ rg.secret_old_rest('admin.getAppProperties', :properties => 'app_id')
248
+
242
249
  === Utility Methods:
243
250
 
244
251
  ==== parse_xxxx
@@ -1,4 +1,4 @@
1
- = rest-graph 1.4.6
1
+ = rest-graph 1.5.0
2
2
  by Cardinal Blue ( http://cardinalblue.com )
3
3
 
4
4
  == LINKS:
@@ -20,8 +20,9 @@ A super simple Facebook Open Graph API client
20
20
 
21
21
  == REQUIREMENTS:
22
22
 
23
- * Tested with MRI 1.8.7 and 1.9.2 and Rubinius HEAD
23
+ * Tested with MRI 1.8.7 and 1.9.2 and Rubinius 1.1.0
24
24
  * gem install rest-client
25
+ * gem install yajl-ruby (optional)
25
26
  * gem install json (optional)
26
27
  * gem install json_pure (optional)
27
28
  * gem install rack (optional, to parse access_token in HTTP_COOKIE)
@@ -97,6 +98,7 @@ Here are ALL the available options for new instance of RestGraph.
97
98
 
98
99
  rg = RestGraph.new(
99
100
  :access_token => TOKEN , # default nil
101
+ :strict => false , # this is the default
100
102
  :graph_server => 'https://graph.facebook.com/', # this is the default
101
103
  :old_server => 'https://api.facebook.com/' , # this is the default
102
104
  :accept => 'text/javascript' , # this is the default
@@ -159,7 +161,7 @@ options for RestGraph instance are also valid options for rest_graph_setup.
159
161
  # site or iframe canvas application, you might want
160
162
  # to just use the Rails (or other framework) session.
161
163
 
162
- === Setup procedures:
164
+ === Alternate ways to setup RestGraph:
163
165
 
164
166
  1. Set upon RestGraph object creation:
165
167
 
@@ -239,6 +241,11 @@ Call functionality from Facebook's old REST API:
239
241
  # if Facebook is not returning a proper JSON
240
242
  # response. Otherwise, this could be omitted.
241
243
 
244
+ # Some Old Rest API requires a special access token with app secret
245
+ # inside of it. For those methods, use secret_old_rest instead of the
246
+ # usual old_rest with common access token.
247
+ rg.secret_old_rest('admin.getAppProperties', :properties => 'app_id')
248
+
242
249
  === Utility Methods:
243
250
 
244
251
  ==== parse_xxxx
data/Rakefile CHANGED
@@ -17,11 +17,14 @@ Bones{
17
17
 
18
18
  depend_on 'rest-client'
19
19
 
20
+ depend_on 'rack' , :development => true
21
+
20
22
  depend_on 'yajl-ruby', :development => true
21
23
  depend_on 'json' , :development => true
22
24
  depend_on 'json_pure', :development => true
23
25
 
24
- depend_on 'rack' , :development => true
26
+ depend_on 'ruby-hmac', :development => true
27
+
25
28
  depend_on 'rr' , :development => true
26
29
  depend_on 'webmock' , :development => true
27
30
  depend_on 'bacon' , :development => true
@@ -47,7 +50,7 @@ end
47
50
 
48
51
  desc 'Run example tests'
49
52
  task 'test:example' => ['gem:install'] do
50
- sh "cd example/rails; #{Gem.ruby} -S rake test"
53
+ sh "cd example/rails2; #{Gem.ruby} -S rake test"
51
54
  end
52
55
 
53
56
  desc 'Run all tests'
File without changes
File without changes
@@ -50,6 +50,10 @@ class ApplicationController < ActionController::Base
50
50
  render :text => Rails.cache.read(Digest::MD5.hexdigest(url))
51
51
  end
52
52
 
53
+ def error
54
+ raise RestGraph::Error.new("don't rescue me")
55
+ end
56
+
53
57
  private
54
58
  def filter_common
55
59
  rest_graph_setup(:auto_authorize => true, :canvas => '')
@@ -62,8 +62,12 @@ module Rails
62
62
  gem 'rails'
63
63
  end
64
64
  rescue Gem::LoadError => load_error
65
- $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
66
- exit 1
65
+ if load_error.message =~ /Could not find RubyGem rails/
66
+ STDERR.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
67
+ exit 1
68
+ else
69
+ raise
70
+ end
67
71
  end
68
72
 
69
73
  class << self
@@ -1,7 +1,7 @@
1
1
  # Be sure to restart your server when you modify this file
2
2
 
3
3
  # Specifies gem version of Rails to use when vendor/rails is not present
4
- RAILS_GEM_VERSION = '2.3.8' unless defined? RAILS_GEM_VERSION
4
+ RAILS_GEM_VERSION = '2.3.9' unless defined? RAILS_GEM_VERSION
5
5
 
6
6
  # Bootstrap the Rails environment, frameworks, and default configuration
7
7
  require File.join(File.dirname(__FILE__), 'boot')
@@ -12,7 +12,7 @@ Rails::Initializer.run do |config|
12
12
  # -- all .rb files in that directory are automatically loaded.
13
13
 
14
14
  # Add additional load paths for your own custom dirs
15
- # config.load_paths += %W( #{RAILS_ROOT}/extras )
15
+ # config.autoload_paths += %W( #{RAILS_ROOT}/extras )
16
16
 
17
17
  # Specify gems that this application depends on and have them installed with rake gems:install
18
18
  # config.gem "bj"
@@ -5,7 +5,7 @@
5
5
  # Make sure the secret is at least 30 characters and all random,
6
6
  # no regular words or you'll be exposed to dictionary attacks.
7
7
  ActionController::Base.session = {
8
- :key => '_rails_session',
8
+ :key => '_rails2_session',
9
9
  :secret => 'c99a19ce0dc4ed1809e32b6b43bd9229c3a504c456230119dd445fdcb63c0ce06b436f1bf1eace27ebbe0da6041ff2b65cbb4ae4beadc3077e3e6ae07ea75118'
10
10
  }
11
11
 
File without changes
File without changes
File without changes
@@ -8,8 +8,11 @@ class ApplicationControllerTest < ActionController::TestCase
8
8
  include WebMock
9
9
 
10
10
  def setup
11
+ body = rand(2) == 0 ? '{"error":{"type":"OAuthException"}}' :
12
+ '{"error_code":104}'
13
+
11
14
  stub_request(:get, 'https://graph.facebook.com/me').
12
- to_return(:body => '{"error":"not authorized"}')
15
+ to_return(:body => body)
13
16
  end
14
17
 
15
18
  def teardown
@@ -131,4 +134,10 @@ class ApplicationControllerTest < ActionController::TestCase
131
134
  assert_response :success
132
135
  assert_equal '["yeti"]', @response.body
133
136
  end
137
+
138
+ def test_error
139
+ get(:error)
140
+ rescue => e
141
+ assert_equal RestGraph::Error, e.class
142
+ end
134
143
  end
@@ -14,15 +14,105 @@ begin
14
14
  rescue LoadError; end
15
15
 
16
16
  # the data structure used in RestGraph
17
- RestGraphStruct = Struct.new(:auto_decode,
17
+ RestGraphStruct = Struct.new(:auto_decode, :strict,
18
18
  :graph_server, :old_server,
19
19
  :accept, :lang,
20
20
  :app_id, :secret,
21
21
  :data, :cache,
22
22
  :error_handler,
23
- :log_handler) unless defined?(RestGraphStruct)
23
+ :log_handler) unless defined?(::RestGraphStruct)
24
24
 
25
25
  class RestGraph < RestGraphStruct
26
+ EventStruct = Struct.new(:duration, :url) unless
27
+ defined?(::RestGraph::EventStruct)
28
+
29
+ Attributes = RestGraphStruct.members.map(&:to_sym) unless
30
+ defined?(::RestGraph::Attributes)
31
+
32
+ class Event < EventStruct; end
33
+ class Event::Requested < Event; end
34
+ class Event::CacheHit < Event; end
35
+
36
+ class Error < RuntimeError
37
+ class AccessToken < Error; end
38
+ class InvalidAccessToken < AccessToken; end
39
+ class MissingAccessToken < AccessToken; end
40
+
41
+ attr_reader :error
42
+ def initialize error
43
+ @error = error
44
+ super(error.inspect)
45
+ end
46
+
47
+ module Util
48
+ extend self
49
+ def parse error
50
+ return Error.new(error) unless error.kind_of?(Hash)
51
+ if invalid_token?(error)
52
+ InvalidAccessToken.new(error)
53
+ elsif missing_token?(error)
54
+ MissingAccessToken.new(error)
55
+ else
56
+ Error.new(error)
57
+ end
58
+ end
59
+
60
+ def invalid_token? error
61
+ (%w[OAuthInvalidTokenException
62
+ OAuthException].include?((error['error'] || {})['type'])) ||
63
+ (error['error_code'] == 190) # Invalid OAuth 2.0 Access Token
64
+ end
65
+
66
+ def missing_token? error
67
+ (error['error'] || {})['message'] =~ /^An active access token/ ||
68
+ (error['error_code'] == 104) # Requires valid signature
69
+ end
70
+ end
71
+ extend Util
72
+ end
73
+
74
+ # honor default attributes
75
+ Attributes.each{ |name|
76
+ module_eval <<-RUBY
77
+ def #{name}
78
+ (r = super).nil? ? (self.#{name} = self.class.default_#{name}) : r
79
+ end
80
+ RUBY
81
+ }
82
+
83
+ # setup defaults
84
+ module DefaultAttributes
85
+ extend self
86
+ def default_auto_decode ; true ; end
87
+ def default_strict ; false ; end
88
+ def default_graph_server; 'https://graph.facebook.com/'; end
89
+ def default_old_server ; 'https://api.facebook.com/' ; end
90
+ def default_accept ; 'text/javascript' ; end
91
+ def default_lang ; 'en-us' ; end
92
+ def default_app_id ; nil ; end
93
+ def default_secret ; nil ; end
94
+ def default_data ; {} ; end
95
+ def default_cache ; nil ; end
96
+ def default_error_handler
97
+ lambda{ |error| raise ::RestGraph::Error.parse(error) }
98
+ end
99
+ def default_log_handler
100
+ lambda{ |event| }
101
+ end
102
+ end
103
+ extend DefaultAttributes
104
+
105
+ # Fallback to ruby-hmac gem in case system openssl
106
+ # lib doesn't support SHA256 (OSX 10.5)
107
+ def self.hmac_sha256 key, data
108
+ # for ruby version >= 1.8.7, we can simply pass sha256,
109
+ # instead of OpenSSL::Digest::Digest.new('sha256')
110
+ # i'll go back to original implementation once all old systems died
111
+ OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, data)
112
+ rescue RuntimeError
113
+ require 'hmac-sha2'
114
+ HMAC::SHA256.digest(key, data)
115
+ end
26
116
 
27
117
  # begin json backend adapter
28
118
  module YajlRuby
@@ -54,7 +144,6 @@ class RestGraph < RestGraphStruct
54
144
  def self.extended mod
55
145
  mod.const_set(:ParseError, Gsub::ParseError)
56
146
  end
57
-
58
147
  # only works for flat hash
59
148
  def json_encode hash
60
149
  middle = hash.inject([]){ |r, (k, v)|
@@ -62,7 +151,7 @@ class RestGraph < RestGraphStruct
62
151
  }.join(',')
63
152
  "{#{middle}}"
64
153
  end
65
- def json_decode
154
+ def json_decode json
66
155
  raise NotImplementedError.new(
67
156
  'You need to install either yajl-ruby, json, or json_pure gem')
68
157
  end
@@ -87,46 +176,9 @@ class RestGraph < RestGraphStruct
87
176
  select_json!(true)
88
177
  end
89
178
  end
90
- select_json!
179
+ select_json! unless respond_to?(:json_decode)
91
180
  # end json backend adapter
92
181
 
93
- class Error < RuntimeError; end
94
- class Event < Struct.new(:duration, :url); end
95
- class Event::Requested < Event; end
96
- class Event::CacheHit < Event; end
97
-
98
- Attributes = RestGraphStruct.members.map(&:to_sym)
99
-
100
- # honor default attributes
101
- Attributes.each{ |name|
102
- module_eval <<-RUBY
103
- def #{name}
104
- (r = super).nil? ? (self.#{name} = self.class.default_#{name}) : r
105
- end
106
- RUBY
107
- }
108
-
109
- # setup defaults
110
- module DefaultAttributes
111
- extend self
112
- def default_auto_decode ; true ; end
113
- def default_graph_server; 'https://graph.facebook.com/'; end
114
- def default_old_server ; 'https://api.facebook.com/' ; end
115
- def default_accept ; 'text/javascript' ; end
116
- def default_lang ; 'en-us' ; end
117
- def default_app_id ; nil ; end
118
- def default_secret ; nil ; end
119
- def default_data ; {} ; end
120
- def default_cache ; nil ; end
121
- def default_error_handler
122
- lambda{ |error| raise ::RestGraph::Error.new(error) }
123
- end
124
- def default_log_handler
125
- lambda{ |event| }
126
- end
127
- end
128
- extend DefaultAttributes
129
-
130
182
 
131
183
 
132
184
 
@@ -151,6 +203,10 @@ class RestGraph < RestGraphStruct
151
203
  !!access_token
152
204
  end
153
205
 
206
+ def secret_access_token
207
+ "#{app_id}|#{secret}"
208
+ end
209
+
154
210
  def lighten!
155
211
  [:cache, :error_handler, :log_handler].each{ |obj| send("#{obj}=", nil) }
156
212
  self
@@ -160,6 +216,13 @@ class RestGraph < RestGraphStruct
160
216
  dup.lighten!
161
217
  end
162
218
 
219
+ def inspect
220
+ super.gsub(/(\w+)=([^,]+)/){ |match|
221
+ value = $2 == 'nil' ? self.class.send("default_#{$1}").inspect : $2
222
+ "#{$1}=#{value}"
223
+ }
224
+ end
225
+
163
226
 
164
227
 
165
228
 
@@ -186,6 +249,26 @@ class RestGraph < RestGraphStruct
186
249
  request(:put , url(path, query, graph_server), opts, payload)
187
250
  end
188
251
 
252
+ def next_page hash, opts={}
253
+ return unless hash['paging'].kind_of?(Hash) && hash['paging']['next']
254
+ request(:get , hash['paging']['next'] , opts)
255
+ end
256
+
257
+ def prev_page hash, opts={}
258
+ return unless hash['paging'].kind_of?(Hash) && hash['paging']['previous']
259
+ request(:get , hash['paging']['previous'] , opts)
260
+ end
261
+ alias_method :previous_page, :prev_page
262
+
263
+ def for_pages hash, pages=1, kind=:next_page, opts={}
264
+ return hash if pages <= 1
265
+ if result = send(kind, hash, opts)
266
+ for_pages(merge_data(result, hash), pages - 1, kind, opts)
267
+ else
268
+ hash
269
+ end
270
+ end
271
+
189
272
 
190
273
 
191
274
 
@@ -225,7 +308,7 @@ class RestGraph < RestGraphStruct
225
308
  "#{str.tr('-_', '+/')}==".unpack('m').first
226
309
  }
227
310
  self.data = self.class.json_decode(json) if
228
- secret && OpenSSL::HMAC.digest('sha256', secret, json_encoded) == sig
311
+ secret && self.class.hmac_sha256(secret, json_encoded) == sig
229
312
  rescue ParseError
230
313
  end
231
314
 
@@ -260,6 +343,11 @@ class RestGraph < RestGraphStruct
260
343
  opts)
261
344
  end
262
345
 
346
+ def secret_old_rest path, query={}, opts={}
347
+ old_rest(path, {:access_token => secret_access_token}.merge(query), opts)
348
+ end
349
+ alias_method :broken_old_rest, :secret_old_rest
350
+
263
351
  def exchange_sessions opts={}
264
352
  query = {:client_id => app_id, :client_secret => secret,
265
353
  :type => 'client_cred'}.merge(opts)
@@ -282,10 +370,9 @@ class RestGraph < RestGraphStruct
282
370
  private
283
371
  def request meth, uri, opts={}, payload=nil
284
372
  start_time = Time.now
285
- post_request(cache_get(uri) || fetch(meth, uri, payload),
286
- opts[:suppress_decode])
373
+ post_request(cache_get(uri) || fetch(meth, uri, payload), opts)
287
374
  rescue RestClient::Exception => e
288
- post_request(e.http_body, opts[:suppress_decode])
375
+ post_request(e.http_body, opts)
289
376
  ensure
290
377
  log_handler.call(Event::Requested.new(Time.now - start_time, uri))
291
378
  end
@@ -304,9 +391,14 @@ class RestGraph < RestGraphStruct
304
391
  headers
305
392
  end
306
393
 
307
- def post_request result, suppress_decode=nil
308
- if auto_decode && !suppress_decode
309
- check_error(self.class.json_decode(result))
394
+ def post_request result, opts={}
395
+ if auto_decode && !opts[:suppress_decode]
396
+ decoded = self.class.json_decode("[#{result}]").first
397
+ check_error(if strict || !decoded.kind_of?(String)
398
+ decoded
399
+ else
400
+ self.class.json_decode(decoded)
401
+ end)
310
402
  else
311
403
  result
312
404
  end
@@ -355,4 +447,13 @@ class RestGraph < RestGraphStruct
355
447
  cache[cache_key(uri)] = result if cache && meth == :get
356
448
  }
357
449
  end
450
+
451
+ def merge_data lhs, rhs
452
+ [lhs, rhs].each{ |hash|
453
+ return rhs.reject{ |k, v| k == 'paging' } if
454
+ !hash.kind_of?(Hash) || !hash['data'].kind_of?(Array)
455
+ }
456
+ lhs['data'].unshift(*rhs['data'])
457
+ lhs
458
+ end
358
459
  end