fogli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +14 -0
  3. data/LICENSE +21 -0
  4. data/README.md +168 -0
  5. data/Rakefile +37 -0
  6. data/VERSION +1 -0
  7. data/examples/README.md +6 -0
  8. data/examples/me/README.md +18 -0
  9. data/examples/me/me.rb +30 -0
  10. data/lib/fogli.rb +46 -0
  11. data/lib/fogli/album.rb +8 -0
  12. data/lib/fogli/categorized_object.rb +9 -0
  13. data/lib/fogli/comment.rb +5 -0
  14. data/lib/fogli/event.rb +9 -0
  15. data/lib/fogli/exception.rb +15 -0
  16. data/lib/fogli/facebook_graph.rb +125 -0
  17. data/lib/fogli/facebook_object.rb +225 -0
  18. data/lib/fogli/facebook_object/connection_proxy.rb +80 -0
  19. data/lib/fogli/facebook_object/connection_scope.rb +123 -0
  20. data/lib/fogli/facebook_object/connections.rb +56 -0
  21. data/lib/fogli/facebook_object/properties.rb +81 -0
  22. data/lib/fogli/facebook_object/scope_methods.rb +49 -0
  23. data/lib/fogli/group.rb +8 -0
  24. data/lib/fogli/link.rb +8 -0
  25. data/lib/fogli/named_object.rb +8 -0
  26. data/lib/fogli/note.rb +7 -0
  27. data/lib/fogli/oauth.rb +167 -0
  28. data/lib/fogli/page.rb +15 -0
  29. data/lib/fogli/photo.rb +8 -0
  30. data/lib/fogli/post.rb +18 -0
  31. data/lib/fogli/status.rb +7 -0
  32. data/lib/fogli/user.rb +36 -0
  33. data/lib/fogli/util/module_attributes.rb +61 -0
  34. data/lib/fogli/util/options.rb +32 -0
  35. data/lib/fogli/video.rb +7 -0
  36. data/test/fogli/facebook_graph_test.rb +124 -0
  37. data/test/fogli/facebook_object/connection_proxy_test.rb +42 -0
  38. data/test/fogli/facebook_object/connection_scope_test.rb +91 -0
  39. data/test/fogli/facebook_object/connections_test.rb +50 -0
  40. data/test/fogli/facebook_object/properties_test.rb +79 -0
  41. data/test/fogli/facebook_object/scope_methods_test.rb +69 -0
  42. data/test/fogli/facebook_object_test.rb +178 -0
  43. data/test/fogli/oauth_test.rb +56 -0
  44. data/test/fogli/post_test.rb +18 -0
  45. data/test/fogli/user_test.rb +25 -0
  46. data/test/fogli/util/options_test.rb +38 -0
  47. data/test/fogli_test.rb +16 -0
  48. data/test/test_helper.rb +14 -0
  49. metadata +150 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .bundle
2
+ *.lock
3
+ test.rb
4
+ .yardoc/
5
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rest-client", "~> 1.5.1"
4
+ gem "json_pure", "~> 1.2.0"
5
+
6
+ group :development do
7
+ gem "sinatra" # for examples
8
+
9
+ gem "yard"
10
+ gem "bluecloth"
11
+ gem "contest", ">= 0.1.2"
12
+ gem "mocha"
13
+ gem "jeweler", "~> 1.4.0"
14
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Mitchell Hashimoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # Fogli
2
+
3
+ Fogli is a **F**acebook **O**pen **G**raph **Li**brary.
4
+
5
+ ## Why?
6
+
7
+ Facebook introduced the Graph API in April, 2010. This API is intended
8
+ to replace their existing "RESTful API" in the future. Mature Facebook
9
+ libraries such as [Facebooker](http://github.com/mmangino/facebooker) are
10
+ heavily tied to the RESTful API and have not started supporting the Open
11
+ Graph.
12
+
13
+ At the time of Fogli's creation, there were a handful of other libraries
14
+ claiming to be Facebook Graph libraries, but I found them unsatisfactory
15
+ since they did not meet any or all of the following requirements:
16
+
17
+ * Minimizes query calls
18
+ * Strong feature support
19
+ * Intuitive object model
20
+ * Independent library; not tied to any web framework
21
+
22
+ Fogli aims to expose the objects of the Facebook Graph through an intuitive
23
+ object oriented interface while minimizing the number of queries needed
24
+ to access data through lazy loading and caching. Fogli is not tied to any
25
+ web framework and can run standalone perfectly fine.
26
+
27
+ ## Supported Features
28
+
29
+ Below is a list of supported Facebook Graph features:
30
+
31
+ * Authorization via OAuth
32
+ * Reading objects (`User`, `Page`, etc.)
33
+ * Reading object connections
34
+ * Deleting objects
35
+ * Scoping top level objects by `fields`
36
+ * Scoping connections by `limit`, `offset`, `since`, `until`, and `fields`
37
+ * Liking/unliking posts
38
+
39
+ The list below is a list of unsupported features, but development is
40
+ planned in the near future:
41
+
42
+ * More support for publishing (posting comments, attending events, etc.)
43
+ * Search
44
+ * Real time updates
45
+ * Analytics
46
+
47
+ Have a feature request? Submit it on the [GitHub Issues](https://github.com/mitchellh/fogli/issues)
48
+ page, or fork and send me a patch!
49
+
50
+ ## Getting Started
51
+
52
+ Install Fogli:
53
+
54
+ gem install fogli
55
+
56
+ Next, you can do a few things:
57
+
58
+ * Open an IRB session and begin playing. Fogli isn't tied to any framework,
59
+ so you can see it work right away:
60
+
61
+ >> require "fogli"
62
+ >> m = Fogli::User["mitchellh"]
63
+ >> m.name
64
+ => "Mitchell Hashimoto"
65
+
66
+ * Run the examples in the `examples/` directory to see how to access basic
67
+ connections and also use {Fogli::OAuth OAuth} to access authorized data.
68
+
69
+ * Read the documentation here! For more specific, class by class documentation,
70
+ please read [the source-generated documentation](http://mitchellh.github.com/fogli/).
71
+
72
+ ## Reading Data and Accessing Connections
73
+
74
+ Reading public information is straightforward and requires no configuration:
75
+
76
+ u = Fogli::User["mitchellh"]
77
+ p u.first_name # "Mitchell"
78
+ p u.last_name # "Hashimoto"
79
+
80
+ Accessing connections (relationships to data) is also intuitive and
81
+ easy:
82
+
83
+ u.feed.each do |item|
84
+ p item
85
+ end
86
+
87
+ ## Authentication, Authorization, and Accessing Private Data
88
+
89
+ To access private data, Facebook requires that the you gain an `access_token`
90
+ by asking the user to authorize your application. This is a two-step process.
91
+ The example before uses [Sinatra](http://www.sinatrarb.com/) with Fogli to
92
+ authorize a user and print his or her email:
93
+
94
+ # Set these configuration values. The keys are retrieved from
95
+ # registering an application with Facebook. These can all be
96
+ # set on the actual method calls later as well.
97
+ Fogli.client_id = "..."
98
+ Fogli.client_secret = "..."
99
+ Fogli.redirect_uri = "http://my-website.com/verify"
100
+
101
+ get "/" do
102
+ # Redirect the user to authorized, asking for email permissions
103
+ redirect Fogli::OAuth.authorize(:scope => "email")
104
+ end
105
+
106
+ get "/verify"
107
+ # Facebook redirects back to this page with a GET parameter `code`
108
+ # which is used to get the access token. This token should also be
109
+ # stored in a cookie or some session storage to be set on subsequent pages.
110
+ Fogli.access_token = Fogli::OAuth.access_token(:code => params[:code])
111
+
112
+ # Print authorized data to prove we're allowed!
113
+ Fogli::User[:me].email
114
+ end
115
+
116
+ Checking if a user is already authenciated is easy as well:
117
+
118
+ # Set from cookies, perhaps
119
+ Fogli.access_token = cookies[:access_token]
120
+
121
+ # Verify its valid
122
+ if Fogli::User.authorized?
123
+ # valid!
124
+ else
125
+ # reauthenticate
126
+ end
127
+
128
+ ## Accessing Connections with Scopes
129
+
130
+ Facebook data connections are typically relationships to massive amounts
131
+ of data, most of which you're not interested in. By scoping the data, you
132
+ can limit the data to only what you want, and optimize the queries made by
133
+ Fogli in the process:
134
+
135
+ # Assuming we're authenticated (above)
136
+ u = Fogli::User[:me]
137
+
138
+ # We just want feed times since yesterday, and we're only interested
139
+ # in 10 of them:
140
+ u.feed.since("yesterday").limit(10).each do |item|
141
+ # Do something with the item
142
+ end
143
+
144
+ # Another example: We want to access all the user's friends, but only
145
+ # want their first and last name:
146
+ u.friends.fields(:first_name, :last_name).each do |friend|
147
+ p "Friend: #{friend.first_name} #{friend.last_name}"
148
+ end
149
+
150
+ ## Lazy Loading and Caching
151
+
152
+ Everything in Fogli is lazy-loaded. This means that data is loaded on
153
+ demand and no queries are made until you need the data. So, how many
154
+ HTTP queries do you think `User[:me].feed.since("yesterday")` takes?
155
+ Since no property was ever access on the user, only 1 query is used
156
+ (to get the `feed` items since yesterday).
157
+
158
+ Additionally, once the data is loaded, it is cached to minimize the
159
+ number of queries needed:
160
+
161
+ # This is also only one query, the entire script, even though the
162
+ # items are accessed 4 times.
163
+ items = Fogli::User[:me].feed.since("yesterday")
164
+ items.each {}
165
+ items.each {}
166
+ items.each {}
167
+ items.each {}
168
+
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rake/testtask'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = "fogli"
7
+ gem.summary = "An efficient, simple, and intuitive Facebook Open Graph library."
8
+ gem.description = "An efficient, simple, and intuitive Facebook Open Graph library."
9
+ gem.email = "mitchell.hashimoto@gmail.com"
10
+ gem.homepage = "http://github.com/mitchellh/fogli"
11
+ gem.authors = ["Mitchell Hashimoto"]
12
+
13
+ gem.add_dependency('rest-client', "~> 1.5.1")
14
+ gem.add_dependency('json_pure', "~> 1.2.0")
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ task :default => :test
22
+
23
+ Rake::TestTask.new do |t|
24
+ t.libs << "libs"
25
+ t.libs << "test"
26
+ t.pattern = 'test/**/*_test.rb'
27
+ end
28
+
29
+ begin
30
+ require 'yard'
31
+ YARD::Rake::YardocTask.new do |t|
32
+ t.options = ['--main', 'README.md', '--markup', 'markdown']
33
+ t.options += ['--title', 'Fogli Documentation']
34
+ end
35
+ rescue LoadError
36
+ puts "Yard not available. Install it with: gem install yard bluecloth"
37
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,6 @@
1
+ # Fogli Examples
2
+
3
+ The examples are organized into directories by their name. Please
4
+ view the `README` files in an example's respective directory for
5
+ more information on that example.
6
+
@@ -0,0 +1,18 @@
1
+ # Fogli "Me" Example
2
+
3
+ The "Me" example shows how to use Fogli to access data about the
4
+ `me` pseudo-user on Facebook which represents the currently authorized
5
+ user.
6
+
7
+ ## Highlights
8
+
9
+ * OAuth authentication
10
+ * Accessing authorized data
11
+
12
+ ## Running
13
+
14
+ To run the example, make sure you have [Sinatra](http://www.sinatrarb.com)
15
+ installed, then enter this directory and run the main file:
16
+
17
+ $ ruby me.rb
18
+
data/examples/me/me.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'sinatra'
3
+
4
+ # Make sure we load the most recent fogli that this is packaged with
5
+ # if its available.
6
+ $:.unshift(File.join(File.dirname(__FILE__), *%W[.. .. lib]))
7
+ require 'fogli'
8
+
9
+ #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
10
+ # IMPORTANT: Set your app configuration details here:
11
+ #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
12
+ Fogli.client_id = "132862110062471"
13
+ Fogli.client_secret = "3f2f371b6f01c449eb3bcfc841b1b9c1"
14
+
15
+ # No need to edit this:
16
+ Fogli.redirect_uri = "http://localhost:4567/verify"
17
+
18
+ get "/" do
19
+ # Authorize the user so we can get an access token
20
+ redirect Fogli::OAuth.authorize(:scope => "email")
21
+ end
22
+
23
+ get "/verify" do
24
+ # Get and store the access token, then print out their user
25
+ # information to verify it worked
26
+ Fogli.access_token = Fogli::OAuth.access_token(:code => params[:code])
27
+
28
+ # Output authorized information to verify it worked
29
+ Fogli::User[:me].email
30
+ end
data/lib/fogli.rb ADDED
@@ -0,0 +1,46 @@
1
+ # Try to load the Rails ActiveSupport module accessors
2
+ # But fall back to our own if it doesn't exist.
3
+ begin
4
+ require 'active_support/core_ext/module/attribute_accessors'
5
+ rescue LoadError
6
+ require 'fogli/util/module_attributes'
7
+ end
8
+
9
+ module Fogli
10
+ # The configuration options below only need to be set if you
11
+ # wish to retrieve authorized information via the Graph API.
12
+ # The configuration keys should be self explanatory.
13
+ mattr_accessor :client_id
14
+ mattr_accessor :client_secret
15
+ mattr_accessor :redirect_uri
16
+ mattr_accessor :access_token
17
+
18
+ # Logger. Set this to anything which has the same API as the Ruby
19
+ # standard library logger, or `nil` to disable logging (default)
20
+ mattr_accessor :logger
21
+
22
+ autoload :Exception, 'fogli/exception'
23
+ autoload :FacebookGraph, 'fogli/facebook_graph'
24
+ autoload :FacebookObject, 'fogli/facebook_object'
25
+
26
+ # User-friendly models
27
+ autoload :Album, 'fogli/album'
28
+ autoload :CategorizedObject, 'fogli/categorized_object'
29
+ autoload :Comment, 'fogli/comment'
30
+ autoload :Event, 'fogli/event'
31
+ autoload :Group, 'fogli/group'
32
+ autoload :Link, 'fogli/link'
33
+ autoload :NamedObject, 'fogli/named_object'
34
+ autoload :Note, 'fogli/note'
35
+ autoload :OAuth, 'fogli/oauth'
36
+ autoload :Page, 'fogli/page'
37
+ autoload :Photo, 'fogli/photo'
38
+ autoload :Post, 'fogli/post'
39
+ autoload :Status, 'fogli/status'
40
+ autoload :User, 'fogli/user'
41
+ autoload :Video, 'fogli/video'
42
+
43
+ module Util
44
+ autoload :Options, 'fogli/util/options'
45
+ end
46
+ end
@@ -0,0 +1,8 @@
1
+ module Fogli
2
+ class Album < FacebookObject
3
+ property :from, :name, :description, :location, :link, :count, :created_time
4
+
5
+ connection :photos, :class => :Photo
6
+ connection :comments, :class => :Comment
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Fogli
2
+ # A categorized object. In addition to simple objects such as
3
+ # {NamedObject}s, Facebook also has simple objects which only have a
4
+ # name and category in addition to an ID. This object represents
5
+ # such an object.
6
+ class CategorizedObject < NamedObject
7
+ property :category
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Fogli
2
+ class Comment < FacebookObject
3
+ property :from, :message, :created_time
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Fogli
2
+ class Event < FacebookObject
3
+ property :owner, :name, :description, :start_time, :end_time, :location,
4
+ :venue, :privacy
5
+
6
+ connection :feed, :class => :Post
7
+ conncetion :noreply, :maybe, :invited, :attending, :declined, :class => :User
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Fogli
2
+ # An exception from a call to the Facebook Graph API. This will
3
+ # always contain the exception type and the message given.
4
+ class Exception < ::Exception
5
+ attr_reader :type
6
+ attr_reader :message
7
+
8
+ def initialize(type, message)
9
+ @type = type
10
+ @message = "[#{type}] #{message}"
11
+
12
+ super()
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,125 @@
1
+ require 'cgi'
2
+ require 'restclient'
3
+ require 'json'
4
+
5
+ module Fogli
6
+ # The most basic level access to the Facebook Graph. This class only
7
+ # has access to the `get`, `post`, etc. methods to query the
8
+ # Facebook Graph directly. This class is used by every other class
9
+ # to access the graph. This class also will automatically insert the
10
+ # Facebook `access_token` if it has been specified, and handles
11
+ # changing the HTTP method to HTTPS in that case.
12
+ #
13
+ # **Note:** Most users should never have to use this class
14
+ # directly. Instead, use one of the models, such as {User}, to
15
+ # access your data.
16
+ #
17
+ # # Requesting Data
18
+ #
19
+ # Getting data from the Facebook graph is simple:
20
+ #
21
+ # Fogli::FacebookGraph.get("/mitchellh")
22
+ #
23
+ # If you have an access token, {FacebookGraph} will add it to the
24
+ # query string without you having to do anything:
25
+ #
26
+ # Fogli.access_token = "..."
27
+ # Fogli::FacebookGraph.get("/me")
28
+ #
29
+ class FacebookGraph
30
+ GRAPH_DOMAIN = "graph.facebook.com"
31
+
32
+ class << self
33
+ # Override default HTTParty behavior to go through our request
34
+ # method to make sure that the access token is properly set if
35
+ # needed.
36
+ [:get, :post, :delete, :head].each do |method|
37
+ # A requester, where the arguments go through the {request}
38
+ # method, which handles automatically adding the
39
+ # {Fogli.access_token} if it is specified, and also handles
40
+ # the URL relative to the given path.
41
+ define_method(method) do |*args|
42
+ Fogli.logger.debug("Fogli #{method}: #{args.inspect}") if Fogli.logger
43
+ request(method, *args)
44
+ end
45
+
46
+ # A raw requester, which just directly requests with the given
47
+ # arguments.
48
+ define_method("raw_#{method}".to_sym) do |*args|
49
+ Fogli.logger.debug("Fogli raw #{method}: #{args.inspect}") if Fogli.logger
50
+ error_check { RestClient.send(method, *args) }
51
+ end
52
+ end
53
+
54
+ # Issues the specified request type with the given URL and
55
+ # options. This method will inject the Facebook `access_token`
56
+ # if it is available, and will properly change the method to
57
+ # "https" if needed.
58
+ def request(type, url, params=nil)
59
+ params ||= {}
60
+
61
+ if Fogli.access_token
62
+ params ||= {}
63
+ params.merge!(:access_token => Fogli.access_token)
64
+ end
65
+
66
+ method = "http"
67
+ method = "https" if params[:access_token]
68
+
69
+ url = "#{method}://#{GRAPH_DOMAIN}#{url}"
70
+ if !params.empty? && [:get, :delete].include?(type)
71
+ # For GET and DELETE, we're responsible for appending any
72
+ # query parameters onto the end of the URL
73
+ params = params.inject([]) do |acc, data|
74
+ k, v = data
75
+ acc << "#{k}=#{CGI.escape(v.to_s)}"
76
+ acc
77
+ end
78
+
79
+ url += "?#{params.join("&")}"
80
+ params = {}
81
+ end
82
+
83
+ error_check { RestClient.send(type, url, params) }
84
+ end
85
+
86
+ # Yields a block, checking for any errors in a request. If no
87
+ # errors are detected, decodes JSON and returns the result.
88
+ def error_check
89
+ response = begin
90
+ yield
91
+ rescue RestClient::Exception => e
92
+ e.response
93
+ end
94
+
95
+ data = parse_response(response)
96
+ if data.is_a?(Hash) && data["error"]
97
+ data = data["error"]
98
+ raise Exception.new(data["type"], data["message"])
99
+ elsif response.code.to_i == 500
100
+ raise Exception.new("Unknown", "Internal server error")
101
+ end
102
+
103
+ data
104
+ rescue NoMethodError
105
+ data
106
+ end
107
+
108
+ # Parses a response depending on the content type it sent back.
109
+ def parse_response(response)
110
+ type = response.headers[:content_type]
111
+ return response.body if type.include?("text/plain")
112
+ return JSON.parse(response.body)
113
+ rescue JSON::ParserError
114
+ response.body
115
+ end
116
+ end
117
+
118
+ # Shortcut methods to access the class-level methods.
119
+ [:get, :post, :delete, :head].each do |method|
120
+ define_method(method) do |*args|
121
+ self.class.send(method, *args)
122
+ end
123
+ end
124
+ end
125
+ end