koala 1.1.0 → 1.2.0beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.travis.yml +2 -1
  2. data/CHANGELOG +26 -0
  3. data/Gemfile +6 -2
  4. data/Rakefile +0 -1
  5. data/koala.gemspec +8 -8
  6. data/lib/koala.rb +42 -45
  7. data/lib/koala/batch_operation.rb +15 -15
  8. data/lib/koala/graph_api.rb +81 -58
  9. data/lib/koala/graph_batch_api.rb +10 -10
  10. data/lib/koala/graph_collection.rb +6 -6
  11. data/lib/koala/http_service.rb +177 -0
  12. data/lib/koala/oauth.rb +2 -2
  13. data/lib/koala/realtime_updates.rb +20 -17
  14. data/lib/koala/rest_api.rb +1 -1
  15. data/lib/koala/test_users.rb +33 -16
  16. data/lib/koala/uploadable_io.rb +47 -42
  17. data/lib/koala/utils.rb +11 -0
  18. data/readme.md +38 -38
  19. data/spec/cases/api_base_spec.rb +2 -2
  20. data/spec/cases/error_spec.rb +32 -0
  21. data/spec/cases/graph_and_rest_api_spec.rb +20 -3
  22. data/spec/cases/graph_api_batch_spec.rb +88 -97
  23. data/spec/cases/graph_api_spec.rb +21 -4
  24. data/spec/cases/http_service_spec.rb +446 -0
  25. data/spec/cases/koala_spec.rb +33 -38
  26. data/spec/cases/oauth_spec.rb +219 -200
  27. data/spec/cases/realtime_updates_spec.rb +45 -31
  28. data/spec/cases/rest_api_spec.rb +23 -7
  29. data/spec/cases/test_users_spec.rb +112 -52
  30. data/spec/cases/uploadable_io_spec.rb +49 -36
  31. data/spec/cases/utils_spec.rb +10 -0
  32. data/spec/fixtures/facebook_data.yml +23 -22
  33. data/spec/fixtures/mock_facebook_responses.yml +126 -96
  34. data/spec/spec_helper.rb +29 -5
  35. data/spec/support/graph_api_shared_examples.rb +59 -52
  36. data/spec/support/json_testing_fix.rb +35 -11
  37. data/spec/support/koala_test.rb +163 -0
  38. data/spec/support/mock_http_service.rb +6 -4
  39. data/spec/support/ordered_hash.rb +205 -0
  40. data/spec/support/rest_api_shared_examples.rb +37 -37
  41. data/spec/support/uploadable_io_shared_examples.rb +2 -8
  42. metadata +78 -79
  43. data/lib/koala/http_services.rb +0 -46
  44. data/lib/koala/http_services/net_http_service.rb +0 -92
  45. data/lib/koala/http_services/typhoeus_service.rb +0 -37
  46. data/spec/cases/http_services/http_service_spec.rb +0 -129
  47. data/spec/cases/http_services/net_http_service_spec.rb +0 -532
  48. data/spec/cases/http_services/typhoeus_service_spec.rb +0 -152
  49. data/spec/support/live_testing_data_helper.rb +0 -40
  50. data/spec/support/setup_mocks_or_live.rb +0 -51
@@ -1,21 +1,21 @@
1
1
  module Koala
2
2
  module Facebook
3
3
  module GraphBatchAPIMethods
4
-
4
+
5
5
  def self.included(base)
6
6
  base.class_eval do
7
7
  alias_method :graph_call_outside_batch, :graph_call
8
8
  alias_method :graph_call, :graph_call_in_batch
9
-
9
+
10
10
  alias_method :check_graph_api_response, :check_response
11
11
  alias_method :check_response, :check_graph_batch_api_response
12
12
  end
13
13
  end
14
-
14
+
15
15
  def batch_calls
16
16
  @batch_calls ||= []
17
17
  end
18
-
18
+
19
19
  def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
20
20
  # for batch APIs, we queue up the call details (incl. post-processing)
21
21
  batch_calls << BatchOperation.new(
@@ -26,7 +26,7 @@ module Koala
26
26
  :http_options => options,
27
27
  :post_processing => post_processing
28
28
  )
29
- nil # batch operations return nothing immediately
29
+ nil # batch operations return nothing immediately
30
30
  end
31
31
 
32
32
  def check_graph_batch_api_response(response)
@@ -45,7 +45,7 @@ module Koala
45
45
  args.merge!(batch_op.files) if batch_op.files
46
46
  batch_op.to_batch_params(access_token)
47
47
  })
48
-
48
+
49
49
  graph_call_outside_batch('/', args, 'post', http_options) do |response|
50
50
  # map the results with post-processing included
51
51
  index = 0 # keep compat with ruby 1.8 - no with_index for map
@@ -57,10 +57,10 @@ module Koala
57
57
  if call_result
58
58
  # (see note in regular api method about JSON parsing)
59
59
  body = MultiJson.decode("[#{call_result['body'].to_s}]")[0]
60
-
60
+
61
61
  unless call_result["code"].to_i >= 500 || error = check_response(body)
62
62
  # Get the HTTP component they want
63
- data = case batch_op.http_options[:http_component]
63
+ data = case batch_op.http_options[:http_component]
64
64
  when :status
65
65
  call_result["code"].to_i
66
66
  when :headers
@@ -78,10 +78,10 @@ module Koala
78
78
  else
79
79
  nil
80
80
  end
81
- end
81
+ end
82
82
  end
83
83
  end
84
-
84
+
85
85
  end
86
86
  end
87
87
  end
@@ -12,23 +12,23 @@ module Koala
12
12
  # by calling next_page or previous_page.
13
13
  attr_reader :paging
14
14
  attr_reader :api
15
-
15
+
16
16
  def initialize(response, api)
17
17
  super response["data"]
18
18
  @paging = response["paging"]
19
19
  @api = api
20
20
  end
21
-
21
+
22
22
  # defines methods for NEXT and PREVIOUS pages
23
23
  %w{next previous}.each do |this|
24
-
24
+
25
25
  # def next_page
26
26
  # def previous_page
27
27
  define_method "#{this.to_sym}_page" do
28
28
  base, args = send("#{this}_page_params")
29
29
  base ? @api.get_page([base, args]) : nil
30
30
  end
31
-
31
+
32
32
  # def next_page_params
33
33
  # def previous_page_params
34
34
  define_method "#{this.to_sym}_page_params" do
@@ -36,7 +36,7 @@ module Koala
36
36
  parse_page_url(@paging[this])
37
37
  end
38
38
  end
39
-
39
+
40
40
  def parse_page_url(url)
41
41
  match = url.match(/.com\/(.*)\?(.*)/)
42
42
  base = match[1]
@@ -48,7 +48,7 @@ module Koala
48
48
  end
49
49
  [base,new_params]
50
50
  end
51
-
51
+
52
52
  end
53
53
  end
54
54
  end
@@ -0,0 +1,177 @@
1
+ require 'faraday'
2
+ require 'faraday_stack'
3
+
4
+ module Koala
5
+ class Response
6
+ attr_reader :status, :body, :headers
7
+ def initialize(status, body, headers)
8
+ @status = status
9
+ @body = body
10
+ @headers = headers
11
+ end
12
+ end
13
+
14
+ module HTTPService
15
+ # common functionality for all HTTP services
16
+
17
+ class << self
18
+ attr_accessor :faraday_middleware, :http_options
19
+ end
20
+
21
+ @http_options ||= {}
22
+
23
+ DEFAULT_MIDDLEWARE = Proc.new do |builder|
24
+ builder.request :multipart
25
+ builder.request :url_encoded
26
+ builder.adapter Faraday.default_adapter
27
+ end
28
+
29
+ def self.server(options = {})
30
+ server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
31
+ server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
32
+ "#{options[:use_ssl] ? "https" : "http"}://#{options[:beta] ? "beta." : ""}#{server}"
33
+ end
34
+
35
+ def self.make_request(path, args, verb, options = {})
36
+ # if the verb isn't get or post, send it as a post argument
37
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
38
+
39
+ # turn all the keys to strings (Faraday has issues with symbols under 1.8.7) and resolve UploadableIOs
40
+ params = args.inject({}) {|hash, kv| hash[kv.first.to_s] = kv.last.is_a?(UploadableIO) ? kv.last.to_upload_io : kv.last; hash}
41
+
42
+ # figure out our options for this request
43
+ request_options = {:params => (verb == "get" ? params : {})}.merge(http_options || {}).merge(process_options(options))
44
+ request_options[:use_ssl] = true if args["access_token"] # require http if there's a token
45
+
46
+ # set up our Faraday connection
47
+ # we have to manually assign params to the URL or the
48
+ conn = Faraday.new(server(request_options), request_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
49
+
50
+ response = conn.send(verb, path, (verb == "post" ? params : {}))
51
+ Koala::Response.new(response.status.to_i, response.body, response.headers)
52
+ end
53
+
54
+ def self.encode_params(param_hash)
55
+ # unfortunately, we can't use to_query because that's Rails, not Ruby
56
+ # if no hash (e.g. no auth token) return empty string
57
+ # this is used mainly by the Batch API nowadays
58
+ ((param_hash || {}).collect do |key_and_value|
59
+ key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
60
+ "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
61
+ end).join("&")
62
+ end
63
+
64
+ # deprecations
65
+ # not elegant or compact code, but temporary
66
+
67
+ def self.always_use_ssl
68
+ Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
69
+ http_options[:use_ssl]
70
+ end
71
+
72
+ def self.always_use_ssl=(value)
73
+ Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
74
+ http_options[:use_ssl] = value
75
+ end
76
+
77
+ def self.timeout
78
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
79
+ http_options[:timeout]
80
+ end
81
+
82
+ def self.timeout=(value)
83
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
84
+ http_options[:timeout] = value
85
+ end
86
+
87
+ def self.timeout
88
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
89
+ http_options[:timeout]
90
+ end
91
+
92
+ def self.timeout=(value)
93
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
94
+ http_options[:timeout] = value
95
+ end
96
+
97
+ def self.proxy
98
+ Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
99
+ http_options[:proxy]
100
+ end
101
+
102
+ def self.proxy=(value)
103
+ Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
104
+ http_options[:proxy] = value
105
+ end
106
+
107
+ def self.ca_path
108
+ Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
109
+ (http_options[:ssl] || {})[:ca_path]
110
+ end
111
+
112
+ def self.ca_path=(value)
113
+ Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
114
+ (http_options[:ssl] ||= {})[:ca_path] = value
115
+ end
116
+
117
+ def self.ca_file
118
+ Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
119
+ (http_options[:ssl] || {})[:ca_file]
120
+ end
121
+
122
+ def self.ca_file=(value)
123
+ Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
124
+ (http_options[:ssl] ||= {})[:ca_file] = value
125
+ end
126
+
127
+ def self.verify_mode
128
+ Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
129
+ (http_options[:ssl] || {})[:verify_mode]
130
+ end
131
+
132
+ def self.verify_mode=(value)
133
+ Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
134
+ (http_options[:ssl] ||= {})[:verify_mode] = value
135
+ end
136
+
137
+ def self.process_options(options)
138
+ if typhoeus_options = options.delete(:typhoeus_options)
139
+ Koala::Utils.deprecate("typhoeus_options should now be included directly in the http_options hash. Support for this key will be removed in a future version.")
140
+ options = options.merge(typhoeus_options)
141
+ end
142
+
143
+ if ca_file = options.delete(:ca_file)
144
+ Koala::Utils.deprecate("http_options[:ca_file] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_file]. Support for this key will be removed in a future version.")
145
+ (options[:ssl] ||= {})[:ca_file] = ca_file
146
+ end
147
+
148
+ if ca_path = options.delete(:ca_path)
149
+ Koala::Utils.deprecate("http_options[:ca_path] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_path]. Support for this key will be removed in a future version.")
150
+ (options[:ssl] ||= {})[:ca_path] = ca_path
151
+ end
152
+
153
+ if verify_mode = options.delete(:verify_mode)
154
+ Koala::Utils.deprecate("http_options[:verify_mode] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:verify_mode]. Support for this key will be removed in a future version.")
155
+ (options[:ssl] ||= {})[:verify_mode] = verify_mode
156
+ end
157
+
158
+ options
159
+ end
160
+ end
161
+
162
+ module TyphoeusService
163
+ def self.deprecated_interface
164
+ # support old-style interface with a warning
165
+ Koala::Utils.deprecate("the TyphoeusService module is deprecated; to use Typhoeus, set Faraday.default_adapter = :typhoeus. Enabling Typhoeus for all Faraday connections.")
166
+ Faraday.default_adapter = :typhoeus
167
+ end
168
+ end
169
+
170
+ module NetHTTPService
171
+ def self.deprecated_interface
172
+ # support old-style interface with a warning
173
+ Koala::Utils.deprecate("the NetHTTPService module is deprecated; to use Net::HTTP, set Faraday.default_adapter = :net_http. Enabling Net::HTTP for all Faraday connections.")
174
+ Faraday.default_adapter = :net_http
175
+ end
176
+ end
177
+ end
data/lib/koala/oauth.rb CHANGED
@@ -52,7 +52,7 @@ module Koala
52
52
  permissions = options[:permissions]
53
53
  scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
54
54
  display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
55
-
55
+
56
56
  callback = options[:callback] || @oauth_callback_url
57
57
  raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
58
58
 
@@ -178,4 +178,4 @@ module Koala
178
178
  end
179
179
  end
180
180
  end
181
- end
181
+ end
@@ -1,15 +1,13 @@
1
- require 'koala'
2
-
3
1
  module Koala
4
2
  module Facebook
5
3
  module RealtimeUpdateMethods
6
4
  # note: to subscribe to real-time updates, you must have an application access token
7
-
5
+
8
6
  def self.included(base)
9
7
  # make the attributes readable
10
8
  base.class_eval do
11
- attr_reader :app_id, :app_access_token, :secret
12
-
9
+ attr_reader :api, :app_id, :app_access_token, :secret
10
+
13
11
  # parses the challenge params and makes sure the call is legitimate
14
12
  # returns the challenge string to be sent back to facebook if true
15
13
  # returns false otherwise
@@ -20,7 +18,7 @@ module Koala
20
18
  # you can make sure this is legitimate through two ways
21
19
  # if your store the token across the calls, you can pass in the token value
22
20
  # and we'll make sure it matches
23
- (verify_token && params["hub.verify_token"] == verify_token) ||
21
+ (verify_token && params["hub.verify_token"] == verify_token) ||
24
22
  # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
25
23
  # you can pass in a block, which we'll call with the verify_token sent by Facebook
26
24
  # if it's legit, return anything that evaluates to true; otherwise, return nil or false
@@ -32,7 +30,7 @@ module Koala
32
30
  end
33
31
  end
34
32
  end
35
-
33
+
36
34
  def initialize(options = {})
37
35
  @app_id = options[:app_id]
38
36
  @app_access_token = options[:app_access_token]
@@ -40,22 +38,22 @@ module Koala
40
38
  unless @app_id && (@app_access_token || @secret) # make sure we have what we need
41
39
  raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
42
40
  end
43
-
41
+
44
42
  # fetch the access token if we're provided a secret
45
43
  if @secret && !@app_access_token
46
44
  oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
47
45
  @app_access_token = oauth.get_app_access_token
48
46
  end
49
-
50
- @graph_api = GraphAPI.new(@app_access_token)
47
+
48
+ @graph_api = API.new(@app_access_token)
51
49
  end
52
-
50
+
53
51
  # subscribes for realtime updates
54
52
  # your callback_url must be set up to handle the verification request or the subscription will not be set up
55
53
  # http://developers.facebook.com/docs/api/realtime
56
54
  def subscribe(object, fields, callback_url, verify_token)
57
55
  args = {
58
- :object => object,
56
+ :object => object,
59
57
  :fields => fields,
60
58
  :callback_url => callback_url,
61
59
  :verify_token => verify_token
@@ -63,7 +61,7 @@ module Koala
63
61
  # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
64
62
  @graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
65
63
  end
66
-
64
+
67
65
  # removes subscription for object
68
66
  # if object is nil, it will remove all subscriptions
69
67
  def unsubscribe(object = nil)
@@ -71,16 +69,21 @@ module Koala
71
69
  args[:object] = object if object
72
70
  @graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
73
71
  end
74
-
72
+
75
73
  def list_subscriptions
76
74
  @graph_api.graph_call(subscription_path)["data"]
77
75
  end
78
-
76
+
77
+ def graph_api
78
+ Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
79
+ @api
80
+ end
81
+
79
82
  protected
80
-
83
+
81
84
  def subscription_path
82
85
  @subscription_path ||= "#{@app_id}/subscriptions"
83
86
  end
84
87
  end
85
88
  end
86
- end
89
+ end
@@ -4,7 +4,7 @@ module Koala
4
4
 
5
5
  module RestAPIMethods
6
6
  def fql_query(fql, args = {}, options = {})
7
- rest_call('fql.query', args.merge(:query => fql), options)
7
+ rest_call('fql.query', args.merge(:query => fql), options)
8
8
  end
9
9
 
10
10
  def fql_multiquery(queries = {}, args = {}, options = {})
@@ -3,7 +3,14 @@ require 'koala'
3
3
  module Koala
4
4
  module Facebook
5
5
  module TestUserMethods
6
-
6
+
7
+ def self.included(base)
8
+ base.class_eval do
9
+ # make the Graph API accessible in case someone wants to make other calls to interact with their users
10
+ attr_reader :api, :app_id, :app_access_token, :secret
11
+ end
12
+ end
13
+
7
14
  def initialize(options = {})
8
15
  @app_id = options[:app_id]
9
16
  @app_access_token = options[:app_access_token]
@@ -17,29 +24,34 @@ module Koala
17
24
  oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
18
25
  @app_access_token = oauth.get_app_access_token
19
26
  end
20
- @graph_api = GraphAPI.new(@app_access_token)
27
+ @api = API.new(@app_access_token)
21
28
  end
22
29
 
23
30
  def create(installed, permissions = nil, args = {}, options = {})
24
31
  # Creates and returns a test user
25
32
  args['installed'] = installed
26
33
  args['permissions'] = (permissions.is_a?(Array) ? permissions.join(",") : permissions) if installed
27
- result = @graph_api.graph_call(accounts_path, args, "post", options)
34
+ result = @api.graph_call(accounts_path, args, "post", options)
28
35
  end
29
-
36
+
30
37
  def list
31
- @graph_api.graph_call(accounts_path)["data"]
38
+ @api.graph_call(accounts_path)["data"]
32
39
  end
33
-
40
+
34
41
  def delete(test_user)
35
42
  test_user = test_user["id"] if test_user.is_a?(Hash)
36
- @graph_api.delete_object(test_user)
43
+ @api.delete_object(test_user)
37
44
  end
38
-
45
+
39
46
  def delete_all
40
- list.each {|u| delete u }
47
+ list.each {|u| delete u}
48
+ end
49
+
50
+ def update(test_user, args = {}, http_options = {})
51
+ test_user = test_user["id"] if test_user.is_a?(Hash)
52
+ @api.graph_call(test_user, args, "post", http_options)
41
53
  end
42
-
54
+
43
55
  def befriend(user1_hash, user2_hash)
44
56
  user1_id = user1_hash["id"] || user1_hash[:id]
45
57
  user2_id = user2_hash["id"] || user2_hash[:id]
@@ -51,11 +63,11 @@ module Koala
51
63
  # but the Facebook call would fail
52
64
  raise ArgumentError, "TestUsers#befriend requires hash arguments for both users with id and access_token"
53
65
  end
54
-
55
- u1_graph_api = GraphAPI.new(user1_token)
56
- u2_graph_api = GraphAPI.new(user2_token)
57
66
 
58
- u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post") &&
67
+ u1_graph_api = API.new(user1_token)
68
+ u2_graph_api = API.new(user2_token)
69
+
70
+ u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post") &&
59
71
  u2_graph_api.graph_call("#{user2_id}/friends/#{user1_id}", {}, "post")
60
72
  end
61
73
 
@@ -73,9 +85,14 @@ module Koala
73
85
  end
74
86
  return users
75
87
  end
76
-
88
+
89
+ def graph_api
90
+ Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
91
+ @api
92
+ end
93
+
77
94
  protected
78
-
95
+
79
96
  def accounts_path
80
97
  @accounts_path ||= "/#{@app_id}/accounts/test-users"
81
98
  end