rest-more 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.travis.yml +4 -2
  2. data/CHANGES.md +54 -34
  3. data/Gemfile +3 -1
  4. data/README.md +187 -14
  5. data/Rakefile +1 -1
  6. data/TODO.md +2 -12
  7. data/doc/ToC.md +9 -0
  8. data/doc/dependency.md +4 -0
  9. data/doc/design.md +4 -0
  10. data/doc/rest-graph.md +4 -0
  11. data/doc/tutorial/facebook.md +173 -0
  12. data/example/async.rb +89 -0
  13. data/example/facebook.rb +13 -0
  14. data/example/multi.rb +28 -0
  15. data/example/rails3/Gemfile +1 -1
  16. data/example/rainbows.rb +40 -0
  17. data/example/simple.rb +8 -0
  18. data/lib/rest-core/client/bing.rb +15 -7
  19. data/lib/rest-core/client/dropbox.rb +104 -0
  20. data/lib/rest-core/client/facebook.rb +59 -15
  21. data/lib/rest-core/client/flurry.rb +11 -1
  22. data/lib/rest-core/client/github.rb +11 -1
  23. data/lib/rest-core/client/linkedin.rb +10 -6
  24. data/lib/rest-core/client/twitter.rb +18 -9
  25. data/lib/rest-core/util/config.rb +15 -9
  26. data/lib/rest-more.rb +1 -0
  27. data/lib/rest-more/version.rb +1 -1
  28. data/rest-more.gemspec +54 -41
  29. data/test/{client/bing → bing}/test_api.rb +0 -0
  30. data/test/dropbox/test_api.rb +37 -0
  31. data/test/{client/facebook → facebook}/config/rest-core.yaml +0 -0
  32. data/test/{client/facebook → facebook}/test_api.rb +0 -0
  33. data/test/{client/facebook → facebook}/test_cache.rb +0 -0
  34. data/test/{client/facebook → facebook}/test_default.rb +0 -0
  35. data/test/{client/facebook → facebook}/test_error.rb +0 -0
  36. data/test/{client/facebook → facebook}/test_handler.rb +0 -0
  37. data/test/{client/facebook → facebook}/test_load_config.rb +0 -0
  38. data/test/{client/facebook → facebook}/test_misc.rb +0 -0
  39. data/test/{client/facebook → facebook}/test_oauth.rb +0 -0
  40. data/test/{client/facebook → facebook}/test_old.rb +0 -0
  41. data/test/{client/facebook → facebook}/test_page.rb +16 -29
  42. data/test/{client/facebook → facebook}/test_parse.rb +2 -2
  43. data/test/{client/facebook → facebook}/test_serialize.rb +0 -0
  44. data/test/{client/facebook → facebook}/test_timeout.rb +0 -0
  45. data/test/{client/flurry → flurry}/test_metrics.rb +0 -0
  46. data/test/{client/mixi → mixi}/test_api.rb +0 -0
  47. data/test/{client/twitter → twitter}/test_api.rb +4 -3
  48. metadata +60 -41
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ task 'gem:spec' do
14
14
  s.version = RestMore::VERSION
15
15
  s.homepage = 'https://github.com/cardinalblue/rest-more'
16
16
 
17
- %w[rest-core].each{ |g| s.add_runtime_dependency(g) }
17
+ %w[rest-core].each{ |g| s.add_runtime_dependency(g, '>=1.0.0') }
18
18
 
19
19
  s.authors = ['Cardinal Blue', 'Lin Jen-Shin (godfat)']
20
20
  s.email = ['dev (XD) cardinalblue.com']
data/TODO.md CHANGED
@@ -1,14 +1,4 @@
1
1
  # TODO
2
2
 
3
- ## high
4
-
5
- * middleware revisit (how to initialize?)
6
-
7
- ## medium
8
-
9
- * what about async request? yield callback? coroutine?
10
- * dependency?
11
-
12
- ## low
13
-
14
- * test utility
3
+ * document about how to migrate from rest-graph to rest-more
4
+ * more tests
@@ -0,0 +1,9 @@
1
+
2
+ # rest-more documentation
3
+
4
+ ## Table of Content
5
+
6
+ * [Facebook tutorial for the beginners, using Rails 3](tutorial/facebook.md)
7
+ * [Migrate from rest-graph](rest-graph.md)
8
+ * [Overview and Design Concept](design.md)
9
+ * [Picking Dependency](dependency.md)
@@ -0,0 +1,4 @@
1
+
2
+ # Picking Dependency
3
+
4
+
@@ -0,0 +1,4 @@
1
+
2
+ # Overview and Design Concept
3
+
4
+
@@ -0,0 +1,4 @@
1
+
2
+ # Migrate from rest-graph
3
+
4
+
@@ -0,0 +1,173 @@
1
+ # How to build a Facebook application within Rails 3 using the rest-more gem
2
+
3
+ 1. Before you start, I strongly recommend reading these:
4
+
5
+ * Apps on Facebook.com -> <http://developers.facebook.com/docs/guides/canvas/>
6
+ * Graph API -> <http://developers.facebook.com/docs/reference/api/>
7
+ * Authentication -> <http://developers.facebook.com/docs/authentication/>
8
+ * Heroku: Building a Facebook Application -> <http://devcenter.heroku.com/articles/facebook/>
9
+
10
+
11
+ 2. Go to [FB Developers website](http://facebook.com/developers) and create a new FB app. Set its canvas name, canvas url and your site URL. Make sure the canvas type is set to "iframe" (which should be the case by default).
12
+
13
+
14
+ 3. Build a new Rails application.
15
+
16
+ ``` shell
17
+ rails new <name>
18
+ ```
19
+
20
+ 4. Declare rest-more and its dependencies in the Gemfile. Add these lines:
21
+
22
+ ``` ruby
23
+ gem 'rest-more'
24
+
25
+ # these gems are used in rest-more
26
+ gem 'json' # you may also use other JSON parsers/generators, i.e. 'yajl-ruby' or 'json_pure'
27
+ ```
28
+
29
+ And run:
30
+
31
+ ``` shell
32
+ bundle install
33
+ ```
34
+
35
+ 5. In order to configure your Rails application for the Facebook application you created, you must create a `rest-core.yaml` file in your /config directory and fill it with your Facebook configuration. If you plan to run your application in the Facebook canvas, also provide a canvas name.
36
+
37
+ Example:
38
+
39
+ ``` yaml
40
+ development:
41
+ facebook:
42
+ app_id: 'XXXXXXXXXXXXXX'
43
+ secret: 'YYYYYYYYYYYYYYYYYYYYYYYYYYY'
44
+ callback_host: 'my.dev.host.com'
45
+
46
+ production:
47
+ facebook:
48
+ app_id: 'XXXXXXXXXXXXXX'
49
+ secret: 'YYYYYYYYYYYYYYYYYYYYYYYYYYY'
50
+ canvas: 'yourcanvasname'
51
+ callback_host: 'my.production.host.com'
52
+ ```
53
+
54
+ If you push to Heroku, your production callback_host should be `yourappname.heroku.com`. You can also access your app directly running `rails server` (or just `rails s`) in your console, but if you do not have an external IP address (e.g. you are behind a NAT), you will need to use a service called a tunnel in order to make your application accessible to the outer world (and Facebook callbacks). You'll find more information on setting up a tunnel here: <http://tunnlr.com/>.
55
+
56
+ 6. Let's create a first controller for your app - ScratchController.
57
+
58
+ ``` shell
59
+ rails generate controller Scratch
60
+ ```
61
+
62
+ 7. The next step will be to include rest-more in your controller. You should put this line in:
63
+
64
+ ``` ruby
65
+ include RC::Facebook::RailsUtil
66
+ ```
67
+
68
+ Now you can make use of the rest-more commands :)
69
+
70
+ 8. To actually use rest-more in a controller action, you will need to first call "rc_facebook_setup", which reads the configuration from the rest-core.yaml and creates a Facebook client. Let's set this up in a before_filter.
71
+
72
+ Add this line after the `include RestGraph::RailsUtil`:
73
+
74
+ ``` ruby
75
+ before_filter :filter_setup_facebook
76
+ ```
77
+
78
+ And declare filter_setup_facebook as a private function:
79
+
80
+ ``` ruby
81
+ private
82
+ def filter_setup_facebook
83
+ rc_facebook_setup(:auto_authorize => true)
84
+ end
85
+ ```
86
+
87
+ The `:auto_authorize` argument of `rc_facebook_setup` tells rest-more to redirect users to the app authorization page if the app is not authorized yet.
88
+
89
+ Hooray! You can now perform all kinds of Graph API operations using the Facebook client.
90
+
91
+ 9. Let's start with following sample action in the Scratch controller:
92
+
93
+ ``` ruby
94
+ def me
95
+ render :text => rc_facebook.get('me').inspect
96
+ end
97
+ ```
98
+
99
+ 10. To run this, go to the /config/routes.rb file to set up the default routing. For now you will just need this line:
100
+
101
+ ``` ruby
102
+ match ':controller/:action'
103
+ ```
104
+
105
+ 11. Commit your change in git, and then push to Heroku:
106
+
107
+ ``` shell
108
+ git add .
109
+ git commit -m "first test of rest-more using scratch/me"
110
+ git push heroku master
111
+ ```
112
+
113
+ After you push your app to Heroku, you can open <http://yourappname.heroku.com/scratch/me> in your browser. If you are not yet logged into Facebook, it will ask you to log in. If you are logged in your Facebook account, this address should redirect you to the authorization page and ask if you want to let your application access your public information. After you confirm, you should be redirected back to 'scratch/me' action which will show your basic information.
114
+
115
+ 12. To see other information, such as your home feed, is very easy. You can add another sample action to your controller:
116
+
117
+ ``` ruby
118
+ def feed
119
+ render :text => rc_facebook.get('me/home').inspect
120
+ end
121
+ ```
122
+
123
+ If you will push the changes to Heroku and go to <http://yourappname.heroku.com/scratch/feed>, the page should display a JSON hash with all the data from your Facebook home feed.
124
+
125
+
126
+ 13. Now let's try to access your Facebook wall. You need to add a new action to your controller:
127
+
128
+ ``` ruby
129
+ def wall
130
+ render :text => rc_facebook.get('me/feed').inspect
131
+ end
132
+ ```
133
+
134
+ Note that Facebook's naming is such that your home news feeds is accessed via `me/home` and your profile walls is accessed via `me/feed` ...
135
+
136
+ Actually, I need to warn you that this time the action won't work properly. Why? Because users didn't grant you the permission to access their walls! You need to ask them for this special permission and that means you need to add something more to your controller.
137
+
138
+ So, we will organize all the permissions we need as a scope and pass them to the `rc_facebook_setup` call. I find it handy to make the scope array and declare what kind of permissions I need just inside this array. If you feel it's a good idea, you can add this line to your private setup function, just before you call `rc_facebook_setup`:
139
+
140
+ ``` ruby
141
+ scope = []
142
+ scope << 'read_stream'
143
+ ```
144
+
145
+ The only permission you need right now is the 'read_stream' permission. You can find out more about different kinds of user permissions here: <http://developers.facebook.com/docs/authentication/permissions/>
146
+
147
+ You also need to add the `auto_authorize_scope` argument to the `rc_facebook_setup`. It will look this way now:
148
+
149
+ ``` ruby
150
+ rc_facebook_setup(:auto_authorize => true, :auto_authorize_scope => scope.join(','))
151
+ ```
152
+
153
+ As you see, you might as well pass the argument like this `:auto_authorize_scope => 'read_stream'`, but once you have to get a lot of different permissions, it's very useful to put them all in an array, because it's more readable and you can easily delete or comment out any one of them.
154
+
155
+ Now save your work and push it to Heroku or just try in your tunneled development environment. /scratch/wall URL should give you the hash with user's wall data now!
156
+
157
+ Remember. Anytime you need to get data of a new kind, you need to ask user for a certain permission first and that means you need to declare this permission in your scope array!
158
+
159
+ 14. What else? If you know how to deal with hashes then you will definitely know how to get any kind of data you want using the rest_graph object. Let's say you want to get a last object from a user's wall (last in terms of time, last posted, so the first on the wall and therefore first to Ruby). Let's take a look at the /scratch/feed page. The hash which is printed on this page has 2 keys - data and paging. Let's leave the paging key aside. What's more interesting here comes as a value of 'data'. So the last object in any user's wall will be simply:
160
+
161
+ ``` ruby
162
+ rc_facebook.get('me/feed')['data'].first
163
+ ```
164
+
165
+ Now let's say you want only to keep the name of the author of this particular object. You can get it by using:
166
+
167
+ ``` ruby
168
+ rc_facebook.get('me/feed')['data'].first['from']['name']
169
+ ```
170
+
171
+ That's it!
172
+
173
+ 15. More information on customizing rest-more and its functions are to be found here: <https://github.com/cardinalblue/rest-more>
@@ -0,0 +1,89 @@
1
+
2
+ require 'rest-more'
3
+
4
+ # RC::Builder.default_app = RC::Auto # for global default_app setting
5
+
6
+ def aget_facebook opts={}
7
+ RC::Facebook.new(opts).get('4'){ |response|
8
+ if response.kind_of?(::Timeout::Error)
9
+ puts "TIMEOUT aget"
10
+ else
11
+ p response
12
+ puts "DONE aget"
13
+ end
14
+ yield if block_given?
15
+ }
16
+ end
17
+
18
+ def get_facebook opts={}
19
+ p RC::Facebook.new(opts).get('4')
20
+ puts "DONE get"
21
+ rescue Timeout::Error
22
+ puts "TIMEOUT get"
23
+ end
24
+
25
+
26
+
27
+ puts "RC::CoolioAsync"
28
+ RC::Facebook.builder.default_app = RC::CoolioAsync
29
+ aget_facebook(:timeout => 0.01)
30
+ aget_facebook(:timeout => 10.0)
31
+ Coolio::Loop.default.run
32
+ puts
33
+
34
+
35
+
36
+ puts "RC::CoolioFiber"
37
+ RC::Facebook.builder.default_app = RC::CoolioFiber
38
+ Fiber.new{ get_facebook(:timeout => 0.01) }.resume
39
+ Fiber.new{ get_facebook(:timeout => 10.0) }.resume
40
+ Coolio::Loop.default.run
41
+ puts
42
+
43
+
44
+
45
+ puts "RC::Coolio"
46
+ RC::Facebook.builder.default_app = RC::Coolio
47
+ aget_facebook(:timeout => 0.01)
48
+ aget_facebook(:timeout => 10.0)
49
+ Fiber.new{ get_facebook(:timeout => 0.01) }.resume
50
+ Fiber.new{ get_facebook(:timeout => 10.0) }.resume
51
+ Coolio::Loop.default.run
52
+ puts
53
+
54
+
55
+
56
+ puts "RC::EmHttpRequestAsync"
57
+ RC::Facebook.builder.default_app = RC::EmHttpRequestAsync
58
+ EM.run{ aget_facebook(:timeout => 0.01){ EM.stop } }
59
+ EM.run{ aget_facebook(:timeout => 10.0){ EM.stop } }
60
+ puts
61
+
62
+
63
+
64
+ puts "RC::EmHttpRequestFiber"
65
+ RC::Facebook.builder.default_app = RC::EmHttpRequestFiber
66
+ EM.run{ Fiber.new{ get_facebook(:timeout => 0.01); EM.stop }.resume }
67
+ EM.run{ Fiber.new{ get_facebook(:timeout => 10.0); EM.stop }.resume }
68
+ puts
69
+
70
+
71
+
72
+ puts "RC::Auto for Coolio"
73
+ RC::Facebook.builder.default_app = RC::Auto
74
+ Coolio::TimerWatcher.new(1).attach(Coolio::Loop.default).on_timer{detach}
75
+ aget_facebook(:timeout => 0.01)
76
+ aget_facebook(:timeout => 10.0)
77
+ Fiber.new{ get_facebook(:timeout => 0.01) }.resume
78
+ Fiber.new{ get_facebook(:timeout => 10.0) }.resume
79
+ Coolio::Loop.default.run
80
+ puts
81
+ puts "RC::Auto for EventMachine"
82
+ EM.run{ aget_facebook(:timeout => 0.01){ EM.stop } }
83
+ EM.run{ aget_facebook(:timeout => 10.0){ EM.stop } }
84
+ EM.run{ Fiber.new{ get_facebook(:timeout => 0.01); EM.stop }.resume }
85
+ EM.run{ Fiber.new{ get_facebook(:timeout => 10.0); EM.stop }.resume }
86
+ puts
87
+ puts "RC::Auto for RestClient"
88
+ get_facebook(:timeout => 0.01)
89
+ get_facebook(:timeout => 10.0)
@@ -0,0 +1,13 @@
1
+
2
+ require 'rest-more'
3
+ require 'eventmachine'
4
+
5
+ RestCore::Builder.default_app = RestCore::EmHttpRequest
6
+
7
+ EM.run{
8
+ Fiber.new{
9
+ p RestCore::Facebook.new.get('4')
10
+ EM.stop
11
+ }.resume
12
+ puts "It's not blocking..."
13
+ }
@@ -0,0 +1,28 @@
1
+
2
+ require 'rest-more'
3
+ require 'eventmachine'
4
+ RestCore::EmHttpRequest # there might be a autoload bug?
5
+ # omitting this line would cause
6
+ # stack level too deep (SystemStackError)
7
+
8
+ RestCore::Builder.default_app = RestCore::Auto
9
+ facebook = RestCore::Facebook.new(:log_method => method(:puts))
10
+
11
+ EM.run{
12
+ Fiber.new{
13
+ fiber = Fiber.current
14
+ result = {}
15
+ facebook.get('4'){ |response|
16
+ result[0] = response
17
+ fiber.resume(result) if result.size == 2
18
+ }
19
+ puts "It's not blocking..."
20
+ facebook.get('4'){ |response|
21
+ result[1] = response
22
+ fiber.resume(result) if result.size == 2
23
+ }
24
+ p Fiber.yield
25
+ EM.stop
26
+ }.resume
27
+ puts "It's not blocking..."
28
+ }
@@ -1,7 +1,7 @@
1
1
 
2
2
  source 'http://rubygems.org'
3
3
 
4
- gem 'rails', '3.0.9'
4
+ gem 'rails', '3.2.1'
5
5
 
6
6
  gem 'rest-client' # for rest-core
7
7
  gem 'rest-core', :path => '../../rest-core'
@@ -0,0 +1,40 @@
1
+
2
+ worker_processes 4 # assuming four CPU cores
3
+ preload_app true
4
+
5
+ Rainbows! do
6
+ use :EventMachine, :em_client_class => lambda{
7
+ RainbowsEventMachineFiberClient
8
+ }
9
+ worker_connections 100
10
+
11
+ client_max_body_size 20*1024*1024 # 20 megabytes
12
+ client_header_buffer_size 8*1024 # 8 kilobytes
13
+ end
14
+
15
+ require 'rest-core'
16
+ ::RC::Builder.default_app = ::RC::Auto
17
+
18
+ class RainbowsEventMachineFiberClient < Rainbows::EventMachine::Client
19
+ def app_call input
20
+ Fiber.new{ super }.resume
21
+ end
22
+ end
23
+
24
+ # monkey patch eventmachine to ignore errors and report them,
25
+ # instead of crashing!!
26
+ module ::EventMachine
27
+ class << self
28
+ alias_method :crashing_stop, :stop
29
+ end
30
+
31
+ def self.stop
32
+ if @wrapped_exception
33
+ $stderr.puts("WARN: #{@wrapped_exception.inspect}: " \
34
+ "#{@wrapped_exception.backtrace.inspect}")
35
+ @wrapped_exception = nil
36
+ else
37
+ crashing_stop
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+
2
+ require 'rest-more'
3
+
4
+ p RestCore::Twitter.new.statuses('_cardinalblue').first # get user tweets
5
+ puts
6
+ p RestCore::Github.new.get('users/cardinalblue') # get user info
7
+ puts
8
+ p RestCore::Facebook.new.get('4') # get user info
@@ -13,7 +13,15 @@ RestCore::Bing = RestCore::Builder.client(:AppId) do
13
13
  use s::CommonLogger , nil
14
14
  use s::Cache , nil, 600 do
15
15
  use s::ErrorHandler, lambda{ |env|
16
- raise ::RestCore::Bing::Error.call(env) }
16
+ if env[s::ASYNC]
17
+ if env[s::RESPONSE_BODY].kind_of?(::Exception)
18
+ env
19
+ else
20
+ env.merge(s::RESPONSE_BODY => ::RestCore::Bing::Error.call(env))
21
+ end
22
+ else
23
+ raise ::RestCore::Bing::Error.call(env)
24
+ end}
17
25
  use s::ErrorDetector, lambda{ |env|
18
26
  if env[s::RESPONSE_BODY].kind_of?(Hash) &&
19
27
  (res = env[s::RESPONSE_BODY]['SearchResponse']).kind_of?(Hash)
@@ -36,17 +44,17 @@ class RestCore::Bing::Error < RestCore::Error
36
44
  class ServiceTemporarilyUnavailable < Bing::Error; end
37
45
  class SourceTypeError < Bing::Error; end
38
46
 
39
- attr_reader :error, :url
40
- def initialize error, url=''
41
- @error, @url = error, url
42
- super("#{error.inspect} from #{url}")
47
+ attr_reader :error, :code, :url
48
+ def initialize error, code, url=''
49
+ @error, @code, @url = error, code, url
50
+ super("[#{code}] #{error.inspect} from #{url}")
43
51
  end
44
52
 
45
53
  def self.call env
46
54
  error, url = env[RESPONSE_BODY], Middleware.request_uri(env)
47
55
  code = extract_error_code(error)
48
56
 
49
- return new(env[FAIL], url) unless code
57
+ return new(error, code, url) unless code
50
58
 
51
59
  case code
52
60
  when 1001; MissingParameter
@@ -58,7 +66,7 @@ class RestCore::Bing::Error < RestCore::Error
58
66
  when 3001; ResultsTemporarilyUnavailable
59
67
  when 3002; ServiceTemporarilyUnavailable
60
68
  when 4001; SourceTypeError
61
- end.new(error, url)
69
+ end.new(error, code, url)
62
70
  end
63
71
 
64
72
  def self.extract_error_code error