koala 1.1.0 → 1.2.0beta1

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