koala 1.0.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 (52) hide show
  1. data/.autotest +12 -0
  2. data/.gitignore +3 -1
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +52 -2
  5. data/Gemfile +8 -0
  6. data/Rakefile +0 -1
  7. data/autotest/discover.rb +1 -0
  8. data/koala.gemspec +14 -14
  9. data/lib/koala/batch_operation.rb +74 -0
  10. data/lib/koala/graph_api.rb +157 -133
  11. data/lib/koala/graph_batch_api.rb +87 -0
  12. data/lib/koala/graph_collection.rb +54 -0
  13. data/lib/koala/http_service.rb +177 -0
  14. data/lib/koala/oauth.rb +181 -0
  15. data/lib/koala/realtime_updates.rb +23 -29
  16. data/lib/koala/rest_api.rb +13 -8
  17. data/lib/koala/test_users.rb +33 -16
  18. data/lib/koala/uploadable_io.rb +152 -87
  19. data/lib/koala/utils.rb +11 -0
  20. data/lib/koala.rb +54 -217
  21. data/readme.md +71 -52
  22. data/spec/cases/api_base_spec.rb +6 -6
  23. data/spec/cases/error_spec.rb +32 -0
  24. data/spec/cases/graph_and_rest_api_spec.rb +20 -3
  25. data/spec/cases/graph_api_batch_spec.rb +600 -0
  26. data/spec/cases/graph_api_spec.rb +21 -4
  27. data/spec/cases/http_service_spec.rb +446 -0
  28. data/spec/cases/koala_spec.rb +50 -0
  29. data/spec/cases/oauth_spec.rb +220 -201
  30. data/spec/cases/realtime_updates_spec.rb +45 -31
  31. data/spec/cases/rest_api_spec.rb +23 -7
  32. data/spec/cases/test_users_spec.rb +112 -52
  33. data/spec/cases/uploadable_io_spec.rb +92 -37
  34. data/spec/cases/utils_spec.rb +10 -0
  35. data/spec/fixtures/cat.m4v +0 -0
  36. data/spec/fixtures/facebook_data.yml +23 -22
  37. data/spec/fixtures/mock_facebook_responses.yml +201 -76
  38. data/spec/spec_helper.rb +30 -5
  39. data/spec/support/graph_api_shared_examples.rb +134 -56
  40. data/spec/support/json_testing_fix.rb +42 -0
  41. data/spec/support/koala_test.rb +163 -0
  42. data/spec/support/mock_http_service.rb +60 -57
  43. data/spec/support/ordered_hash.rb +205 -0
  44. data/spec/support/rest_api_shared_examples.rb +139 -15
  45. data/spec/support/uploadable_io_shared_examples.rb +2 -8
  46. metadata +98 -112
  47. data/lib/koala/http_services.rb +0 -146
  48. data/spec/cases/http_services/http_service_spec.rb +0 -54
  49. data/spec/cases/http_services/net_http_service_spec.rb +0 -350
  50. data/spec/cases/http_services/typhoeus_service_spec.rb +0 -144
  51. data/spec/support/live_testing_data_helper.rb +0 -40
  52. data/spec/support/setup_mocks_or_live.rb +0 -52
data/.autotest ADDED
@@ -0,0 +1,12 @@
1
+ # Override autotest default magic to rerun all tests every time a
2
+ # change is detected on the file system.
3
+ class Autotest
4
+
5
+ def get_to_green
6
+ begin
7
+ rerun_all_tests
8
+ wait_for_changes unless all_good
9
+ end until all_good
10
+ end
11
+
12
+ end
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  pkg
2
2
  .project
3
- Gemfile.lock
3
+ Gemfile.lock
4
+ .rvmrc
5
+ *.rbc
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ rvm:
2
+ - 1.8.7 # (current default)
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - ruby-head
6
+ - rbx
7
+ - rbx-2.0
8
+ - ree
9
+ - jruby
data/CHANGELOG CHANGED
@@ -1,3 +1,52 @@
1
+ v1.2
2
+ New methods:
3
+ -- API is now the main API class, contains both Graph and REST methods
4
+ -- Old classes are aliased with deprecation warnings (non-breaking change)
5
+ -- TestUsers#update lets you update the name or password of an existing test user
6
+ -- API.get_page_access_token lets you easily fetch the access token for a page you manage (thanks, marcgg!)
7
+ Updated methods:
8
+ -- API.put_picture now accepts URLs to images (thanks, marcgg!)
9
+ Internal improvements:
10
+ -- Koala now uses Faraday to make requests, replacing the HTTPServices (see wiki)
11
+ -- Koala::HTTPService.http_options allows specification of default Faraday connection options
12
+ -- Koala::HTTPService.faraday_middleware allows custom middleware configurations
13
+ -- Koala now defaults to Net::HTTP rather than Typhoeus
14
+ -- Koala::NetHTTPService and Koala::TyphoeusService modules no longer exist
15
+ -- Koala no longer automatically switches to Net::HTTP when uploading IO objects to Facebook
16
+ -- RealTimeUpdates and TestUsers are no longer subclasses of API, but have their own .api objects
17
+ -- The old .graph_api accessor is aliases to .api with a deprecation warning
18
+ Testing improvements:
19
+ -- Live test suites now run against test users by default
20
+ -- Test suite can be repeatedly run live without having to update facebook_data.yml
21
+ -- OAuth code and session key tests cannot be run against test users
22
+ -- Faraday adapter for live tests can be specified with ADAPTER=[your adapter] in the shell command
23
+ -- Tests now pass against all rubies on Travis CI
24
+ -- Expanded test coverage
25
+ -- Fixed bug with YAML parsing in Ruby 1.9
26
+
27
+ v1.1
28
+ New methods:
29
+ -- Added Batch API support (thanks, seejohnrun and spiegela!)
30
+ -- includes file uploads, error handling, and FQL
31
+ -- Added GraphAPI#put_video
32
+ -- Added GraphAPI#get_comments_for_urls (thanks, amrnt!)
33
+ -- Added RestAPI#fql_multiquery, which simplifies the results (thanks, amrnt!)
34
+ -- HTTP services support global proxy and timeout settings (thanks, itchy!)
35
+ -- Net::HTTP supports global ca_path, ca_file, and verify_mode settings (thanks, spiegela!)
36
+ Updated methods:
37
+ -- RealtimeUpdates now uses a GraphAPI object instead of its own API
38
+ -- RestAPI#rest_call now has an optional last argument for method, for calls requiring POST, DELETE, etc. (thanks, sshilo!)
39
+ -- Filename can now be specified when uploading (e.g. for Ads API) (thanks, sshilo!)
40
+ -- get_objects([]) returns [] instead of a Facebook error in non-batch mode (thanks, aselder!)
41
+ Internal improvements:
42
+ -- Koala is now more compatible with other Rubies (JRuby, Rubinius, etc.)
43
+ -- HTTP services are more modular and can be changed on the fly (thanks, chadk!)
44
+ -- Includes support for uploading StringIOs and other non-files via Net::HTTP even when using TyphoeusService
45
+ -- Koala now uses multi_json to improve compatibility with Rubinius and other Ruby versions
46
+ -- Koala now uses the modern Typhoeus API (thanks, aselder!)
47
+ -- Koala now uses the current modern Net::HTTP interface (thanks, romanbsd!)
48
+ -- Fixed bugs and typos (thanks, waynn, mokevnin, and tikh!)
49
+
1
50
  v1.0
2
51
  New methods:
3
52
  -- Photo and file upload now supported through #put_picture
@@ -6,13 +55,13 @@ New methods:
6
55
  -- Added put_connection and delete_connection convenience methods
7
56
  Updated methods:
8
57
  -- Search can now search places, checkins, etc. (thanks, rickyc!)
9
- -- You can now pass :beta => true in the http options to use Facebook's beta tier.
58
+ -- You can now pass :beta => true in the http options to use Facebook's beta tier
10
59
  -- TestUser#befriend now requires user info hashes (id and access token) due to Facebook API changes (thanks, pulsd and kbighorse!)
11
60
  -- All methods now accept an http_options hash as their optional last parameter (thanks, spiegela!)
12
61
  -- url_for_oauth_code can now take a :display option (thanks, netbe!)
13
62
  -- Net::HTTP can now accept :timeout and :proxy options (thanks, gilles!)
14
63
  -- Test users now supports using test accounts across multiple apps
15
- Internal improvements:
64
+ Internal improvements:
16
65
  -- For public requests, Koala now uses http by default (instead of https) to improve speed
17
66
  -- This can be overridden through Koala.always_use_ssl= or by passing :use_ssl => true in the options hash for an api call
18
67
  -- Read-only REST API requests now go through the faster api-read server
@@ -22,6 +71,7 @@ Internal improvements:
22
71
  -- Updated parse_signed_request to match Facebook's current implementation (thanks, imajes!)
23
72
  -- APIError is now < StandardError, not Exception
24
73
  -- Added KoalaError for non-API errors
74
+ -- Net::HTTP's SSL verification is no longer disabled by default
25
75
  Test improvements:
26
76
  -- Incorporated joshk's awesome rewrite of the entire Koala test suite (thanks, joshk!)
27
77
  -- Expanded HTTP service tests (added Typhoeus test suite and additional Net::HTTP test cases)
data/Gemfile CHANGED
@@ -1,3 +1,11 @@
1
1
  source :rubygems
2
2
 
3
+ group :development, :test do
4
+ gem "typhoeus"
5
+ end
6
+
7
+ if defined? JRUBY_VERSION
8
+ gem "jruby-openssl"
9
+ end
10
+
3
11
  gemspec
data/Rakefile CHANGED
@@ -7,7 +7,6 @@ rescue LoadError
7
7
  puts 'although not required, bundler is recommened for running the tests'
8
8
  end
9
9
 
10
-
11
10
  task :default => :spec
12
11
 
13
12
  require 'rspec/core/rake_task'
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
data/koala.gemspec CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{koala}
5
- s.version = "1.0.0"
6
- s.date = %q{2011-05-01}
5
+ s.version = "1.2.0beta1"
6
+ s.date = %q{2011-08-17}
7
7
 
8
8
  s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the REST API, realtime updates, and OAuth authentication.}
9
9
  s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph and REST APIs, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
@@ -28,23 +28,23 @@ Gem::Specification.new do |s|
28
28
  s.specification_version = 3
29
29
 
30
30
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
31
- s.add_runtime_dependency(%q<json>, ["~> 1.0"])
32
- s.add_runtime_dependency(%q<multipart-post>, ["~> 1.0"])
33
- s.add_development_dependency(%q<rspec>, ["~> 2.5.0"])
31
+ s.add_runtime_dependency(%q<multi_json>, ["~> 1.0"])
32
+ s.add_runtime_dependency(%q<faraday>, ["~> 0.7.4"])
33
+ s.add_runtime_dependency(%q<faraday-stack>, ["~> 0.1.3"])
34
+ s.add_development_dependency(%q<rspec>, ["~> 2.5"])
34
35
  s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
35
- s.add_development_dependency(%q<typhoeus>, ["~> 0.2.4"])
36
36
  else
37
- s.add_dependency(%q<json>, ["~> 1.0"])
38
- s.add_dependency(%q<multipart-post>, ["~> 1.0"])
39
- s.add_dependency(%q<rspec>, ["~> 2.5.0"])
37
+ s.add_dependency(%q<multi_json>, ["~> 1.0"])
38
+ s.add_dependency(%q<rspec>, ["~> 2.5"])
40
39
  s.add_dependency(%q<rake>, ["~> 0.8.7"])
41
- s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
40
+ s.add_dependency(%q<faraday>, ["~> 0.7.4"])
41
+ s.add_dependency(%q<faraday-stack>, ["~> 0.1.3"])
42
42
  end
43
43
  else
44
- s.add_dependency(%q<json>, ["~> 1.0"])
45
- s.add_dependency(%q<multipart-post>, ["~> 1.0"])
46
- s.add_dependency(%q<rspec>, ["~> 2.5.0"])
44
+ s.add_dependency(%q<multi_json>, ["~> 1.0"])
45
+ s.add_dependency(%q<rspec>, ["~> 2.5"])
47
46
  s.add_dependency(%q<rake>, ["~> 0.8.7"])
48
- s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
47
+ s.add_dependency(%q<faraday>, ["~> 0.7.4"])
48
+ s.add_dependency(%q<faraday-stack>, ["~> 0.1.3"])
49
49
  end
50
50
  end
@@ -0,0 +1,74 @@
1
+ module Koala
2
+ module Facebook
3
+ class BatchOperation
4
+ attr_reader :access_token, :http_options, :post_processing, :files, :batch_api, :identifier
5
+
6
+ @identifier = 0
7
+
8
+ def self.next_identifier
9
+ @identifier += 1
10
+ end
11
+
12
+ def initialize(options = {})
13
+ @identifier = self.class.next_identifier
14
+ @args = (options[:args] || {}).dup # because we modify it below
15
+ @access_token = options[:access_token]
16
+ @http_options = (options[:http_options] || {}).dup # dup because we modify it below
17
+ @batch_args = @http_options.delete(:batch_args) || {}
18
+ @url = options[:url]
19
+ @method = options[:method].to_sym
20
+ @post_processing = options[:post_processing]
21
+
22
+ process_binary_args
23
+
24
+ raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
25
+ end
26
+
27
+ def to_batch_params(main_access_token)
28
+ # set up the arguments
29
+ args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
30
+
31
+ response = {
32
+ :method => @method.to_s,
33
+ :relative_url => @url,
34
+ }
35
+
36
+ # handle batch-level arguments, such as name, depends_on, and attached_files
37
+ @batch_args[:attached_files] = @files.keys.join(",") if @files
38
+ response.merge!(@batch_args) if @batch_args
39
+
40
+ # for get and delete, we append args to the URL string
41
+ # otherwise, they go in the body
42
+ if args_string.length > 0
43
+ if args_in_url?
44
+ response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
45
+ else
46
+ response[:body] = args_string if args_string.length > 0
47
+ end
48
+ end
49
+
50
+ response
51
+ end
52
+
53
+ protected
54
+
55
+ def process_binary_args
56
+ # collect binary files
57
+ @args.each_pair do |key, value|
58
+ if UploadableIO.binary_content?(value)
59
+ @files ||= {}
60
+ # we use a class-level counter to ensure unique file identifiers across multiple batch operations
61
+ # (this is thread safe, since we just care about uniqueness)
62
+ # so remove the file from the original hash and add it to the file store
63
+ id = "op#{identifier}_file#{@files.keys.length}"
64
+ @files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
65
+ end
66
+ end
67
+ end
68
+
69
+ def args_in_url?
70
+ @method == :get || @method == :delete
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,53 +1,53 @@
1
1
  module Koala
2
2
  module Facebook
3
- GRAPH_SERVER = "graph.facebook.com"
3
+ GRAPH_SERVER = "graph.facebook.com"
4
4
 
5
5
  module GraphAPIMethods
6
6
  # A client for the Facebook Graph API.
7
- #
7
+ #
8
8
  # See http://github.com/arsduo/koala for Ruby/Koala documentation
9
9
  # and http://developers.facebook.com/docs/api for Facebook API documentation
10
- #
10
+ #
11
11
  # The Graph API is made up of the objects in Facebook (e.g., people, pages,
12
12
  # events, photos) and the connections between them (e.g., friends,
13
13
  # photo tags, and event RSVPs). This client provides access to those
14
14
  # primitive types in a generic way. For example, given an OAuth access
15
15
  # token, this will fetch the profile of the active user and the list
16
16
  # of the user's friends:
17
- #
18
- # graph = Koala::Facebook::GraphAPI.new(access_token)
17
+ #
18
+ # graph = Koala::Facebook::API.new(access_token)
19
19
  # user = graph.get_object("me")
20
20
  # friends = graph.get_connections(user["id"], "friends")
21
- #
21
+ #
22
22
  # You can see a list of all of the objects and connections supported
23
23
  # by the API at http://developers.facebook.com/docs/reference/api/.
24
- #
24
+ #
25
25
  # You can obtain an access token via OAuth or by using the Facebook
26
26
  # JavaScript SDK. See the Koala and Facebook documentation for more information.
27
- #
27
+ #
28
28
  # If you are using the JavaScript SDK, you can use the
29
29
  # Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
30
30
  # for the active user from the cookie saved by the SDK.
31
-
31
+
32
32
  # Objects
33
33
 
34
34
  def get_object(id, args = {}, options = {})
35
35
  # Fetchs the given object from the graph.
36
36
  graph_call(id, args, "get", options)
37
37
  end
38
-
38
+
39
39
  def get_objects(ids, args = {}, options = {})
40
- # Fetchs all of the given object from the graph.
41
- # We return a map from ID to object. If any of the IDs are invalid,
42
- # we raise an exception.
43
- graph_call("", args.merge("ids" => ids.join(",")), "get", options)
40
+ # Fetchs all of the given objects from the graph.
41
+ # If any of the IDs are invalid, they'll raise an exception.
42
+ return [] if ids.empty?
43
+ graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
44
44
  end
45
-
45
+
46
46
  def put_object(parent_object, connection_name, args = {}, options = {})
47
47
  # Writes the given object to the graph, connected to the given parent.
48
48
  # See http://developers.facebook.com/docs/api#publishing for all of
49
49
  # the supported writeable objects.
50
- #
50
+ #
51
51
  # For example,
52
52
  # graph.put_object("me", "feed", :message => "Hello, world")
53
53
  # writes "Hello, world" to the active user's wall.
@@ -56,7 +56,7 @@ module Koala
56
56
  # publishing wall posts requires the "publish_stream" permission. See
57
57
  # http://developers.facebook.com/docs/authentication/ for details about
58
58
  # extended permissions.
59
-
59
+
60
60
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
61
61
  graph_call("#{parent_object}/#{connection_name}", args, "post", options)
62
62
  end
@@ -66,13 +66,14 @@ module Koala
66
66
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
67
67
  graph_call(id, {}, "delete", options)
68
68
  end
69
-
69
+
70
70
  # Connections
71
-
71
+
72
72
  def get_connections(id, connection_name, args = {}, options = {})
73
73
  # Fetchs the connections for given object.
74
- result = graph_call("#{id}/#{connection_name}", args, "get", options)
75
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
74
+ graph_call("#{id}/#{connection_name}", args, "get", options) do |result|
75
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
76
+ end
76
77
  end
77
78
 
78
79
  def put_connections(id, connection_name, args = {}, options = {})
@@ -87,55 +88,56 @@ module Koala
87
88
  graph_call("#{id}/#{connection_name}", args, "delete", options)
88
89
  end
89
90
 
90
- # Pictures
91
- # to delete pictures, use delete_object(photo_id)
92
- # note: you'll need the user_photos permission to actually access photos after uploading them
93
-
91
+ # Media (photos and videos)
92
+ # to delete photos or videos, use delete_object(object_id)
93
+ # note: you'll need the user_photos or user_videos permissions to actually access media after upload
94
+
94
95
  def get_picture(object, args = {}, options = {})
95
96
  # Gets a picture object, returning the URL (which Facebook sends as a header)
96
- result = graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers))
97
- result["Location"]
98
- end
99
-
97
+ graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
98
+ result["Location"]
99
+ end
100
+ end
101
+
102
+ # Can be called in multiple ways:
103
+ #
104
+ # put_picture(file, [content_type], ...)
105
+ # put_picture(path_to_file, [content_type], ...)
106
+ # put_picture(picture_url, ...)
107
+ #
108
+ # You can pass in uploaded files directly from Rails or Sinatra.
109
+ # (See lib/koala/uploadable_io.rb for supported frameworks)
110
+ #
111
+ # Optional parameters can be added to the end of the argument list:
112
+ # - args: a hash of request parameters (default: {})
113
+ # - target_id: ID of the target where to post the picture (default: "me")
114
+ # - options: a hash of http options passed to the HTTPService module
115
+ #
116
+ # put_picture(file, content_type, {:message => "Message"}, 01234560)
117
+ # put_picture(params[:file], {:message => "Message"})
118
+ #
119
+ # (Note that with URLs, there's no optional content type field)
120
+ # put_picture(picture_url, {:message => "Message"}, my_page_id)
121
+
100
122
  def put_picture(*picture_args)
101
- # Can be called in multiple ways:
102
- #
103
- # put_picture(file, [content_type], ...)
104
- # put_picture(path_to_file, [content_type], ...)
105
- #
106
- # You can pass in uploaded files directly from Rails or Sinatra.
107
- # (See lib/koala/uploadable_io.rb for supported frameworks)
108
- #
109
- # Optional parameters can be added to the end of the argument list:
110
- # - args: a hash of request parameters (default: {})
111
- # - target_id: ID of the target where to post the picture (default: "me")
112
- # - options: a hash of http options passed to the HTTPService module
113
- #
114
- # put_picture(file, content_type, {:message => "Message"}, 01234560)
115
- # put_picture(params[:file], {:message => "Message"})
116
-
117
- raise KoalaError.new("Wrong number of arguments for put_picture") unless picture_args.size.between?(1, 5)
118
-
119
- args_offset = picture_args[1].kind_of?(Hash) || picture_args.size == 1 ? 0 : 1
120
-
121
- args = picture_args[1 + args_offset] || {}
122
- target_id = picture_args[2 + args_offset] || "me"
123
- options = picture_args[3 + args_offset] || {}
124
-
125
- args["source"] = Koala::UploadableIO.new(*picture_args.slice(0, 1 + args_offset))
126
-
127
- self.put_object(target_id, "photos", args, options)
123
+ put_object(*parse_media_args(picture_args, "photos"))
128
124
  end
129
-
125
+
126
+ def put_video(*video_args)
127
+ args = parse_media_args(video_args, "videos")
128
+ args.last[:video] = true
129
+ put_object(*args)
130
+ end
131
+
130
132
  # Wall posts
131
133
  # To get wall posts, use get_connections(user, "feed")
132
134
  # To delete a wall post, just use delete_object(post_id)
133
-
135
+
134
136
  def put_wall_post(message, attachment = {}, profile_id = "me", options = {})
135
137
  # attachment is a hash describing the wall post
136
138
  # (see X for more details)
137
- # For instance,
138
- #
139
+ # For instance,
140
+ #
139
141
  # {"name" => "Link name"
140
142
  # "link" => "http://www.example.com/",
141
143
  # "caption" => "{*actor*} posted a new review",
@@ -144,19 +146,19 @@ module Koala
144
146
 
145
147
  self.put_object(profile_id, "feed", attachment.merge({:message => message}), options)
146
148
  end
147
-
149
+
148
150
  # Comments
149
151
  # to delete comments, use delete_object(comment_id)
150
152
  # to get comments, use get_connections(object, "likes")
151
-
153
+
152
154
  def put_comment(object_id, message, options = {})
153
155
  # Writes the given comment on the given post.
154
156
  self.put_object(object_id, "comments", {:message => message}, options)
155
157
  end
156
-
158
+
157
159
  # Likes
158
160
  # to get likes, use get_connections(user, "likes")
159
-
161
+
160
162
  def put_like(object_id, options = {})
161
163
  # Likes the given post.
162
164
  self.put_object(object_id, "likes", {}, options)
@@ -169,89 +171,111 @@ module Koala
169
171
  end
170
172
 
171
173
  # Search
172
-
174
+
173
175
  def search(search_terms, args = {}, options = {})
174
176
  args.merge!({:q => search_terms}) unless search_terms.nil?
175
- result = graph_call("search", args, "get", options)
176
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
177
- end
178
-
179
- # API access
180
-
181
- def graph_call(*args)
182
- # Direct access to the Facebook API
183
- # see any of the above methods for example invocations
184
- response = api(*args) do |response|
185
- # check for Graph API-specific errors
186
- if response.is_a?(Hash) && error_details = response["error"]
187
- raise APIError.new(error_details)
188
- end
177
+ graph_call("search", args, "get", options) do |result|
178
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
189
179
  end
190
-
191
- response
192
- end
193
-
180
+ end
181
+
182
+ # Convenience Methods
183
+
184
+ def get_page_access_token(object_id)
185
+ result = get_object(object_id, :fields => "access_token") do
186
+ result ? result["access_token"] : nil
187
+ end
188
+ end
189
+
190
+ def get_comments_for_urls(urls = [], args = {}, options = {})
191
+ # Fetchs the comments for given URLs (array or comma-separated string)
192
+ # see https://developers.facebook.com/blog/post/490
193
+ return [] if urls.empty?
194
+ args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
195
+ get_object("comments", args, options)
196
+ end
197
+
194
198
  # GraphCollection support
195
-
196
199
  def get_page(params)
197
200
  # Pages through a set of results stored in a GraphCollection
198
201
  # Used for connections and search results
199
- result = graph_call(*params)
200
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
202
+ graph_call(*params) do |result|
203
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
204
+ end
201
205
  end
202
-
203
- end
204
-
205
-
206
- class GraphCollection < Array
207
- #This class is a light wrapper for collections returned
208
- #from the Graph API.
209
- #
210
- #It extends Array to allow direct access to the data colleciton
211
- #which should allow it to drop in seamlessly.
212
- #
213
- #It also allows access to paging information and the
214
- #ability to get the next/previous page in the collection
215
- #by calling next_page or previous_page.
216
- attr_reader :paging
217
- attr_reader :api
218
-
219
- def initialize(response, api)
220
- super response["data"]
221
- @paging = response["paging"]
222
- @api = api
206
+
207
+ # Batch API
208
+ def batch(http_options = {}, &block)
209
+ batch_client = GraphBatchAPI.new(access_token)
210
+ if block
211
+ yield batch_client
212
+ batch_client.execute(http_options)
213
+ else
214
+ batch_client
215
+ end
223
216
  end
224
-
225
- # defines methods for NEXT and PREVIOUS pages
226
- %w{next previous}.each do |this|
227
-
228
- # def next_page
229
- # def previous_page
230
- define_method "#{this.to_sym}_page" do
231
- base, args = send("#{this}_page_params")
232
- base ? @api.get_page([base, args]) : nil
217
+
218
+ def self.included(base)
219
+ base.class_eval do
220
+ def self.batch
221
+ raise NoMethodError, "The BatchAPI signature has changed (the original implementation was not thread-safe). Please see https://github.com/arsduo/koala/wiki/Batch-requests. (This message will be removed in the final 1.1 release.)"
222
+ end
233
223
  end
234
-
235
- # def next_page_params
236
- # def previous_page_params
237
- define_method "#{this.to_sym}_page_params" do
238
- return nil unless @paging and @paging[this]
239
- parse_page_url(@paging[this])
224
+ end
225
+
226
+ # Direct access to the Facebook API
227
+ # see any of the above methods for example invocations
228
+ def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
229
+ result = api(path, args, verb, options) do |response|
230
+ error = check_response(response)
231
+ raise error if error
240
232
  end
233
+
234
+ # now process as appropriate (get picture header, make GraphCollection, etc.)
235
+ post_processing ? post_processing.call(result) : result
241
236
  end
242
-
243
- def parse_page_url(url)
244
- match = url.match(/.com\/(.*)\?(.*)/)
245
- base = match[1]
246
- args = match[2]
247
- params = CGI.parse(args)
248
- new_params = {}
249
- params.each_pair do |key,value|
250
- new_params[key] = value.join ","
237
+
238
+ def check_response(response)
239
+ # check for Graph API-specific errors
240
+ # this returns an error, which is immediately raised (non-batch)
241
+ # or added to the list of batch results (batch)
242
+ if response.is_a?(Hash) && error_details = response["error"]
243
+ APIError.new(error_details)
244
+ end
245
+ end
246
+
247
+ private
248
+
249
+ def parse_media_args(media_args, method)
250
+ # photo and video uploads can accept different types of arguments (see above)
251
+ # so here, we parse the arguments into a form directly usable in put_object
252
+ raise KoalaError.new("Wrong number of arguments for put_#{method == "photos" ? "picture" : "video"}") unless media_args.size.between?(1, 5)
253
+
254
+ args_offset = media_args[1].kind_of?(Hash) || media_args.size == 1 ? 0 : 1
255
+
256
+ args = media_args[1 + args_offset] || {}
257
+ target_id = media_args[2 + args_offset] || "me"
258
+ options = media_args[3 + args_offset] || {}
259
+
260
+ if url?(media_args.first)
261
+ # If media_args is a URL, we can upload without UploadableIO
262
+ args.merge!(:url => media_args.first)
263
+ else
264
+ args["source"] = Koala::UploadableIO.new(*media_args.slice(0, 1 + args_offset))
265
+ end
266
+
267
+ [target_id, method, args, options]
268
+ end
269
+
270
+ def url?(data)
271
+ return false unless data.is_a? String
272
+ begin
273
+ uri = URI.parse(data)
274
+ %w( http https ).include?(uri.scheme)
275
+ rescue URI::BadURIError
276
+ false
251
277
  end
252
- [base,new_params]
253
278
  end
254
-
255
279
  end
256
280
  end
257
281
  end