koala 0.9.1 → 1.0.0

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/.gitignore +3 -0
  2. data/CHANGELOG +42 -7
  3. data/Gemfile +3 -0
  4. data/LICENSE +1 -1
  5. data/Manifest +10 -1
  6. data/Rakefile +13 -14
  7. data/koala.gemspec +36 -19
  8. data/lib/koala/graph_api.rb +188 -123
  9. data/lib/koala/http_services.rb +93 -18
  10. data/lib/koala/rest_api.rb +73 -6
  11. data/lib/koala/test_users.rb +85 -0
  12. data/lib/koala/uploadable_io.rb +115 -0
  13. data/lib/koala.rb +114 -116
  14. data/readme.md +32 -18
  15. data/spec/cases/api_base_spec.rb +101 -0
  16. data/spec/cases/graph_and_rest_api_spec.rb +31 -0
  17. data/spec/cases/graph_api_spec.rb +25 -0
  18. data/spec/cases/http_services/http_service_spec.rb +54 -0
  19. data/spec/cases/http_services/net_http_service_spec.rb +350 -0
  20. data/spec/cases/http_services/typhoeus_service_spec.rb +144 -0
  21. data/spec/cases/oauth_spec.rb +409 -0
  22. data/spec/cases/realtime_updates_spec.rb +184 -0
  23. data/spec/cases/rest_api_spec.rb +25 -0
  24. data/spec/cases/test_users_spec.rb +221 -0
  25. data/spec/cases/uploadable_io_spec.rb +151 -0
  26. data/spec/fixtures/beach.jpg +0 -0
  27. data/spec/{facebook_data.yml → fixtures/facebook_data.yml} +18 -14
  28. data/spec/{mock_facebook_responses.yml → fixtures/mock_facebook_responses.yml} +314 -241
  29. data/spec/spec_helper.rb +18 -0
  30. data/spec/support/graph_api_shared_examples.rb +424 -0
  31. data/spec/support/live_testing_data_helper.rb +40 -0
  32. data/spec/{mock_http_service.rb → support/mock_http_service.rb} +94 -80
  33. data/spec/support/rest_api_shared_examples.rb +161 -0
  34. data/spec/support/setup_mocks_or_live.rb +52 -0
  35. data/spec/support/uploadable_io_shared_examples.rb +76 -0
  36. metadata +131 -43
  37. data/init.rb +0 -2
  38. data/spec/koala/api_base_tests.rb +0 -96
  39. data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +0 -10
  40. data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +0 -11
  41. data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +0 -114
  42. data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +0 -150
  43. data/spec/koala/graph_api/graph_collection_tests.rb +0 -104
  44. data/spec/koala/live_testing_data_helper.rb +0 -23
  45. data/spec/koala/net_http_service_tests.rb +0 -186
  46. data/spec/koala/oauth/oauth_tests.rb +0 -433
  47. data/spec/koala/realtime_updates/realtime_updates_tests.rb +0 -187
  48. data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +0 -94
  49. data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +0 -36
  50. data/spec/koala_spec.rb +0 -18
  51. data/spec/koala_spec_helper.rb +0 -45
  52. data/spec/koala_spec_without_mocks.rb +0 -19
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg
2
+ .project
3
+ Gemfile.lock
data/CHANGELOG CHANGED
@@ -1,3 +1,38 @@
1
+ v1.0
2
+ New methods:
3
+ -- Photo and file upload now supported through #put_picture
4
+ -- Added UploadableIO class to manage file uploads
5
+ -- Added a delete_like method (thanks to waseem)
6
+ -- Added put_connection and delete_connection convenience methods
7
+ Updated methods:
8
+ -- 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.
10
+ -- TestUser#befriend now requires user info hashes (id and access token) due to Facebook API changes (thanks, pulsd and kbighorse!)
11
+ -- All methods now accept an http_options hash as their optional last parameter (thanks, spiegela!)
12
+ -- url_for_oauth_code can now take a :display option (thanks, netbe!)
13
+ -- Net::HTTP can now accept :timeout and :proxy options (thanks, gilles!)
14
+ -- Test users now supports using test accounts across multiple apps
15
+ Internal improvements:
16
+ -- For public requests, Koala now uses http by default (instead of https) to improve speed
17
+ -- This can be overridden through Koala.always_use_ssl= or by passing :use_ssl => true in the options hash for an api call
18
+ -- Read-only REST API requests now go through the faster api-read server
19
+ -- Replaced parse_signed_request with a version from Facebook that also supports the new signed params proposal
20
+ -- Note: invalid requests will now raise exceptions rather than return nil, in keeping with other SDKs
21
+ -- Delete methods will now raise an error if there's no access token (like put_object and delete_like)
22
+ -- Updated parse_signed_request to match Facebook's current implementation (thanks, imajes!)
23
+ -- APIError is now < StandardError, not Exception
24
+ -- Added KoalaError for non-API errors
25
+ Test improvements:
26
+ -- Incorporated joshk's awesome rewrite of the entire Koala test suite (thanks, joshk!)
27
+ -- Expanded HTTP service tests (added Typhoeus test suite and additional Net::HTTP test cases)
28
+ -- Live tests now verify that the access token has the necessary permissions before starting
29
+ -- Replaced the 50-person network test, which often took 15+ minutes to run live, with a 5-person test
30
+
31
+ v0.10.0
32
+ -- Added test user module
33
+ -- Fixed bug when raising APIError after Facebook fails to exchange session keys
34
+ -- Made access_token accessible via the readonly access_token property on all our API classes
35
+
1
36
  v0.9.1
2
37
  -- Tests are now compatible with Ruby 1.9.2
3
38
  -- Added JSON to runtime dependencies
@@ -6,10 +41,10 @@ v0.9.1
6
41
  v0.9.0
7
42
  -- Added parse_signed_request to handle Facebook's new authentication scheme
8
43
  -- note: creates dependency on OpenSSL (OpenSSL::HMAC) for decryption
9
- -- Added GraphCollection class to provide paging support for GraphAPI get_connections and search methods (thanks to jagthedrummer)
44
+ -- Added GraphCollection class to provide paging support for GraphAPI get_connections and search methods (thanks to jagthedrummer)
10
45
  -- Added get_page method to easily fetch pages of results from GraphCollections
11
46
  -- Exchanging sessions for tokens now works properly when provided invalid/expired session keys
12
- -- You can now include a :typhoeus_options key in TyphoeusService#make_request's options hash to control the Typhoeus call (for example, to set :disable_ssl_peer_verification => true)
47
+ -- You can now include a :typhoeus_options key in TyphoeusService#make_request's options hash to control the Typhoeus call (for example, to set :disable_ssl_peer_verification => true)
13
48
  -- All paths provided to HTTP services start with leading / to improve compatibility with stubbing libraries
14
49
  -- If Facebook returns nil for search or get_connections requests, Koala now returns nil rather than throwing an exception
15
50
 
@@ -18,7 +53,7 @@ v0.8.0
18
53
  -- Removed string overloading for the methods, per 0.7.3, which caused Marshaling issues
19
54
  -- Removed ability to provide a string as the second argument to url_for_access_token, per 0.5.0
20
55
 
21
- v0.7.4
56
+ v0.7.4
22
57
  -- Fixed bug with get_user_from_cookies
23
58
 
24
59
  v0.7.3
@@ -29,18 +64,18 @@ v0.7.3
29
64
  -- Using those methods triggers a deprecation warning
30
65
  -- This will be removed by 1.0
31
66
  -- There are new info methods (get_access_token_info, get_app_access_token_info, get_token_info_from_session_keys, and get_user_info_from_cookies) that natively return hashes, for when you want the expiration date
32
- -- Responses with HTTP status 500+ now properly throw errors under Net::HTTP
67
+ -- Responses with HTTP status 500+ now properly throw errors under Net::HTTP
33
68
  -- Updated changelog
34
69
  -- Added license
35
70
 
36
71
  v0.7.2
37
72
  -- Added support for exchanging session keys for OAuth access tokens (get_token_from_session_key for single keys, get_tokens_from_session_keys for multiple)
38
73
  -- Moved Koala files into a koala/ subdirectory to minimize risk of name collisions
39
- -- Added OAuth Playground git submodule as an example
74
+ -- Added OAuth Playground git submodule as an example
40
75
  -- Updated tests, readme, and changelog
41
76
 
42
77
  v0.7.1
43
- -- Updated RealtimeUpdates#list_subscriptions and GraphAPI#get_connections to now return an
78
+ -- Updated RealtimeUpdates#list_subscriptions and GraphAPI#get_connections to now return an
44
79
  array of results directly (rather than a hash with one key)
45
80
  -- Fixed a bug with Net::HTTP-based HTTP service in which the headers hash was improperly formatted
46
81
  -- Updated readme
@@ -87,7 +122,7 @@ v0.3.1
87
122
  v0.3
88
123
  -- Renamed Graph API class from Facebook::GraphAPI to FacebookGraph::API
89
124
  -- Created FacebookGraph::OAuth class for tokens and OAuth URLs
90
- -- Updated method for including HTTP service (think we've got it this time)
125
+ -- Updated method for including HTTP service (think we've got it this time)
91
126
  -- Updated tests
92
127
  -- Added CHANGELOG and gemspec
93
128
 
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2010 Alex Koppel
3
+ Copyright (c) 2010-2011 Alex Koppel
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/Manifest CHANGED
@@ -9,20 +9,29 @@ lib/koala/graph_api.rb
9
9
  lib/koala/http_services.rb
10
10
  lib/koala/realtime_updates.rb
11
11
  lib/koala/rest_api.rb
12
+ lib/koala/test_users.rb
13
+ lib/koala/uploadable_io.rb
12
14
  readme.md
13
15
  spec/facebook_data.yml
14
16
  spec/koala/api_base_tests.rb
17
+ spec/koala/assets/beach.jpg
15
18
  spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb
16
19
  spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb
17
20
  spec/koala/graph_api/graph_api_no_access_token_tests.rb
21
+ spec/koala/graph_api/graph_api_tests.rb
18
22
  spec/koala/graph_api/graph_api_with_access_token_tests.rb
19
23
  spec/koala/graph_api/graph_collection_tests.rb
24
+ spec/koala/http_services/http_service_tests.rb
25
+ spec/koala/http_services/net_http_service_tests.rb
26
+ spec/koala/http_services/typhoeus_service_tests.rb
20
27
  spec/koala/live_testing_data_helper.rb
21
- spec/koala/net_http_service_tests.rb
22
28
  spec/koala/oauth/oauth_tests.rb
23
29
  spec/koala/realtime_updates/realtime_updates_tests.rb
24
30
  spec/koala/rest_api/rest_api_no_access_token_tests.rb
31
+ spec/koala/rest_api/rest_api_tests.rb
25
32
  spec/koala/rest_api/rest_api_with_access_token_tests.rb
33
+ spec/koala/test_users/test_users_tests.rb
34
+ spec/koala/uploadable_io/uploadable_io_tests.rb
26
35
  spec/koala_spec.rb
27
36
  spec/koala_spec_helper.rb
28
37
  spec/koala_spec_without_mocks.rb
data/Rakefile CHANGED
@@ -1,17 +1,16 @@
1
- # Rakefile
2
- require 'rubygems'
3
1
  require 'rake'
4
- require 'echoe'
5
2
 
6
- # gem management
7
- Echoe.new('koala', '0.9.1') do |p|
8
- p.summary = "A lightweight, flexible library for Facebook with support for the Graph API, the old REST API, realtime updates, and OAuth validation."
9
- p.description = "Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, 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."
10
- p.url = "http://github.com/arsduo/koala"
11
- p.author = ["Alex Koppel", "Chris Baclig", "Rafi Jacoby", "Context Optional"]
12
- p.email = "alex@alexkoppel.com"
13
- p.ignore_pattern = ["tmp/*", "script/*", "pkg/*"]
14
- p.runtime_dependencies = ["json"]
15
- p.development_dependencies = []
16
- p.retain_gemspec = true
3
+ begin
4
+ require 'bundler/setup'
5
+ Bundler::GemHelper.install_tasks
6
+ rescue LoadError
7
+ puts 'although not required, bundler is recommened for running the tests'
8
+ end
9
+
10
+
11
+ task :default => :spec
12
+
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new do |t|
15
+ t.rspec_opts = ["--color", '--format doc']
17
16
  end
data/koala.gemspec CHANGED
@@ -1,33 +1,50 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Gem::Specification.new do |s|
4
- s.name = %q{koala}
5
- s.version = "0.9.1"
4
+ s.name = %q{koala}
5
+ s.version = "1.0.0"
6
+ s.date = %q{2011-05-01}
7
+
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
+ 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.}
10
+ s.homepage = %q{http://github.com/arsduo/koala}
6
11
 
7
- s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
12
  s.authors = ["Alex Koppel, Chris Baclig, Rafi Jacoby, Context Optional"]
9
- s.date = %q{2010-10-13}
10
- s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, 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.}
11
- s.email = %q{alex@alexkoppel.com}
12
- s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb"]
13
- s.files = ["CHANGELOG", "LICENSE", "Manifest", "Rakefile", "init.rb", "koala.gemspec", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/api_base_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb", "spec/koala/graph_api/graph_api_no_access_token_tests.rb", "spec/koala/graph_api/graph_api_with_access_token_tests.rb", "spec/koala/graph_api/graph_collection_tests.rb", "spec/koala/live_testing_data_helper.rb", "spec/koala/net_http_service_tests.rb", "spec/koala/oauth/oauth_tests.rb", "spec/koala/realtime_updates/realtime_updates_tests.rb", "spec/koala/rest_api/rest_api_no_access_token_tests.rb", "spec/koala/rest_api/rest_api_with_access_token_tests.rb", "spec/koala_spec.rb", "spec/koala_spec_helper.rb", "spec/koala_spec_without_mocks.rb", "spec/mock_facebook_responses.yml", "spec/mock_http_service.rb"]
14
- s.homepage = %q{http://github.com/arsduo/koala}
15
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala", "--main", "readme.md"]
16
- s.require_paths = ["lib"]
17
- s.rubyforge_project = %q{koala}
18
- s.rubygems_version = %q{1.3.6}
19
- s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the old REST API, realtime updates, and OAuth validation.}
13
+ s.email = %q{alex@alexkoppel.com}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+
18
+ s.extra_rdoc_files = ["readme.md", "CHANGELOG"]
19
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala"]
20
+
21
+ s.require_paths = ["lib"]
22
+
23
+ s.rubygems_version = %q{1.4.2}
24
+
25
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
20
26
 
21
27
  if s.respond_to? :specification_version then
22
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
28
  s.specification_version = 3
24
29
 
25
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
- s.add_runtime_dependency(%q<json>, [">= 0"])
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"])
34
+ s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
35
+ s.add_development_dependency(%q<typhoeus>, ["~> 0.2.4"])
27
36
  else
28
- s.add_dependency(%q<json>, [">= 0"])
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"])
40
+ s.add_dependency(%q<rake>, ["~> 0.8.7"])
41
+ s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
29
42
  end
30
43
  else
31
- s.add_dependency(%q<json>, [">= 0"])
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"])
47
+ s.add_dependency(%q<rake>, ["~> 0.8.7"])
48
+ s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
32
49
  end
33
50
  end
@@ -1,65 +1,12 @@
1
1
  module Koala
2
2
  module Facebook
3
- GRAPH_SERVER = "graph.facebook.com"
4
-
5
- class GraphCollection < Array
6
- #This class is a light wrapper for collections returned
7
- #from the Graph API.
8
- #
9
- #It extends Array to allow direct access to the data colleciton
10
- #which should allow it to drop in seamlessly.
11
- #
12
- #It also allows access to paging information and the
13
- #ability to get the next/previous page in the collection
14
- #by calling next_page or previous_page.
15
- attr_reader :paging
16
- attr_reader :api
17
-
18
- def initialize(response, api)
19
- super response["data"]
20
- @paging = response["paging"]
21
- @api = api
22
- end
23
-
24
- # defines methods for NEXT and PREVIOUS pages
25
- %w{next previous}.each do |this|
26
-
27
- # def next_page
28
- # def previous_page
29
- define_method "#{this.to_sym}_page" do
30
- base, args = send("#{this}_page_params")
31
- base ? @api.get_page([base, args]) : nil
32
- end
33
-
34
- # def next_page_params
35
- # def previous_page_params
36
- define_method "#{this.to_sym}_page_params" do
37
- return nil unless @paging and @paging[this]
38
- parse_page_url(@paging[this])
39
- end
40
- end
41
-
42
- def parse_page_url(url)
43
- match = url.match(/.com\/(.*)\?(.*)/)
44
- base = match[1]
45
- args = match[2]
46
- params = CGI.parse(args)
47
- new_params = {}
48
- params.each_pair do |key,value|
49
- new_params[key] = value.join ","
50
- end
51
- [base,new_params]
52
- end
53
-
54
- end
55
-
56
-
3
+ GRAPH_SERVER = "graph.facebook.com"
57
4
 
58
5
  module GraphAPIMethods
59
6
  # A client for the Facebook Graph API.
60
7
  #
61
- # See http://developers.facebook.com/docs/api for complete documentation
62
- # for the API.
8
+ # See http://github.com/arsduo/koala for Ruby/Koala documentation
9
+ # and http://developers.facebook.com/docs/api for Facebook API documentation
63
10
  #
64
11
  # The Graph API is made up of the objects in Facebook (e.g., people, pages,
65
12
  # events, photos) and the connections between them (e.g., friends,
@@ -76,108 +23,164 @@ module Koala
76
23
  # by the API at http://developers.facebook.com/docs/reference/api/.
77
24
  #
78
25
  # You can obtain an access token via OAuth or by using the Facebook
79
- # JavaScript SDK. See http://developers.facebook.com/docs/authentication/
80
- # for details.
26
+ # JavaScript SDK. See the Koala and Facebook documentation for more information.
81
27
  #
82
28
  # If you are using the JavaScript SDK, you can use the
83
29
  # Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
84
30
  # for the active user from the cookie saved by the SDK.
85
-
86
- def get_object(id, args = {})
31
+
32
+ # Objects
33
+
34
+ def get_object(id, args = {}, options = {})
87
35
  # Fetchs the given object from the graph.
88
- graph_call(id, args)
36
+ graph_call(id, args, "get", options)
89
37
  end
90
38
 
91
- def get_objects(ids, args = {})
39
+ def get_objects(ids, args = {}, options = {})
92
40
  # Fetchs all of the given object from the graph.
93
41
  # We return a map from ID to object. If any of the IDs are invalid,
94
42
  # we raise an exception.
95
- graph_call("", args.merge("ids" => ids.join(",")))
43
+ graph_call("", args.merge("ids" => ids.join(",")), "get", options)
96
44
  end
97
45
 
98
- def get_page(params)
99
- result = graph_call(*params)
100
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
101
- end
102
-
103
- def get_connections(id, connection_name, args = {})
104
- # Fetchs the connections for given object.
105
- result = graph_call("#{id}/#{connection_name}", args)
106
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
107
- end
108
-
109
-
110
- def get_picture(object, args = {})
111
- result = graph_call("#{object}/picture", args, "get", :http_component => :headers)
112
- result["Location"]
113
- end
114
-
115
- def put_object(parent_object, connection_name, args = {})
46
+ def put_object(parent_object, connection_name, args = {}, options = {})
116
47
  # Writes the given object to the graph, connected to the given parent.
117
- #
118
- # For example,
119
- #
120
- # graph.put_object("me", "feed", :message => "Hello, world")
121
- #
122
- # writes "Hello, world" to the active user's wall. Likewise, this
123
- # will comment on a the first post of the active user's feed:
124
- #
125
- # feed = graph.get_connections("me", "feed")
126
- # post = feed["data"][0]
127
- # graph.put_object(post["id"], "comments", :message => "First!")
128
- #
129
48
  # See http://developers.facebook.com/docs/api#publishing for all of
130
49
  # the supported writeable objects.
131
50
  #
51
+ # For example,
52
+ # graph.put_object("me", "feed", :message => "Hello, world")
53
+ # writes "Hello, world" to the active user's wall.
54
+ #
132
55
  # Most write operations require extended permissions. For example,
133
56
  # publishing wall posts requires the "publish_stream" permission. See
134
57
  # http://developers.facebook.com/docs/authentication/ for details about
135
58
  # extended permissions.
59
+
60
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
61
+ graph_call("#{parent_object}/#{connection_name}", args, "post", options)
62
+ end
63
+
64
+ def delete_object(id, options = {})
65
+ # Deletes the object with the given ID from the graph.
66
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
67
+ graph_call(id, {}, "delete", options)
68
+ end
69
+
70
+ # Connections
71
+
72
+ def get_connections(id, connection_name, args = {}, options = {})
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
76
+ end
136
77
 
78
+ def put_connections(id, connection_name, args = {}, options = {})
79
+ # Posts a certain connection
137
80
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
138
- graph_call("#{parent_object}/#{connection_name}", args, "post")
81
+ graph_call("#{id}/#{connection_name}", args, "post", options)
139
82
  end
83
+
84
+ def delete_connections(id, connection_name, args = {}, options = {})
85
+ # Deletes a given connection
86
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
87
+ graph_call("#{id}/#{connection_name}", args, "delete", options)
88
+ end
89
+
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
140
93
 
141
- def put_wall_post(message, attachment = {}, profile_id = "me")
142
- # Writes a wall post to the given profile's wall.
143
- #
144
- # We default to writing to the authenticated user's wall if no
145
- # profile_id is specified.
94
+ def get_picture(object, args = {}, options = {})
95
+ # 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
+
100
+ 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
146
113
  #
147
- # attachment adds a structured attachment to the status message being
148
- # posted to the Wall. It should be a dictionary of the form:
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)
128
+ end
129
+
130
+ # Wall posts
131
+ # To get wall posts, use get_connections(user, "feed")
132
+ # To delete a wall post, just use delete_object(post_id)
133
+
134
+ def put_wall_post(message, attachment = {}, profile_id = "me", options = {})
135
+ # attachment is a hash describing the wall post
136
+ # (see X for more details)
137
+ # For instance,
149
138
  #
150
- # {"name": "Link name"
151
- # "link": "http://www.example.com/",
152
- # "caption": "{*actor*} posted a new review",
153
- # "description": "This is a longer description of the attachment",
154
- # "picture": "http://www.example.com/thumbnail.jpg"}
139
+ # {"name" => "Link name"
140
+ # "link" => "http://www.example.com/",
141
+ # "caption" => "{*actor*} posted a new review",
142
+ # "description" => "This is a longer description of the attachment",
143
+ # "picture" => "http://www.example.com/thumbnail.jpg"}
155
144
 
156
- self.put_object(profile_id, "feed", attachment.merge({:message => message}))
145
+ self.put_object(profile_id, "feed", attachment.merge({:message => message}), options)
157
146
  end
158
-
159
- def put_comment(object_id, message)
147
+
148
+ # Comments
149
+ # to delete comments, use delete_object(comment_id)
150
+ # to get comments, use get_connections(object, "likes")
151
+
152
+ def put_comment(object_id, message, options = {})
160
153
  # Writes the given comment on the given post.
161
- self.put_object(object_id, "comments", {:message => message})
154
+ self.put_object(object_id, "comments", {:message => message}, options)
162
155
  end
163
-
164
- def put_like(object_id)
156
+
157
+ # Likes
158
+ # to get likes, use get_connections(user, "likes")
159
+
160
+ def put_like(object_id, options = {})
165
161
  # Likes the given post.
166
- self.put_object(object_id, "likes")
162
+ self.put_object(object_id, "likes", {}, options)
167
163
  end
168
-
169
- def delete_object(id)
170
- # Deletes the object with the given ID from the graph.
171
- graph_call(id, {}, "delete")
164
+
165
+ def delete_like(object_id, options = {})
166
+ # Unlikes a given object for the logged-in user
167
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
168
+ graph_call("#{object_id}/likes", {}, "delete", options)
172
169
  end
173
-
174
- def search(search_terms, args = {})
175
- # Searches for a given term
176
- result = graph_call("search", args.merge({:q => search_terms}))
170
+
171
+ # Search
172
+
173
+ def search(search_terms, args = {}, options = {})
174
+ args.merge!({:q => search_terms}) unless search_terms.nil?
175
+ result = graph_call("search", args, "get", options)
177
176
  result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
178
- end
177
+ end
178
+
179
+ # API access
179
180
 
180
181
  def graph_call(*args)
182
+ # Direct access to the Facebook API
183
+ # see any of the above methods for example invocations
181
184
  response = api(*args) do |response|
182
185
  # check for Graph API-specific errors
183
186
  if response.is_a?(Hash) && error_details = response["error"]
@@ -186,7 +189,69 @@ module Koala
186
189
  end
187
190
 
188
191
  response
192
+ end
193
+
194
+ # GraphCollection support
195
+
196
+ def get_page(params)
197
+ # Pages through a set of results stored in a GraphCollection
198
+ # 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
189
201
  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
223
+ 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
233
+ 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])
240
+ end
241
+ 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 ","
251
+ end
252
+ [base,new_params]
253
+ end
254
+
190
255
  end
191
256
  end
192
- end
257
+ end