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
@@ -0,0 +1,81 @@
1
+ module Fogli
2
+ class FacebookObject < FacebookGraph
3
+ # Allows for properties to be set on an object. A property is
4
+ # accessible instance data which is parsed from the JSON data blob
5
+ # which is returned by Graph API calls.
6
+ module Properties
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ # Defines a property on the object. These properties map
13
+ # directly to keys in the returned JSON from the Facebook
14
+ # Graph API.
15
+ def property(*names)
16
+ options = names.last
17
+ options = {} if !options.kind_of?(Hash)
18
+
19
+ names.each do |name|
20
+ next if name.kind_of?(Hash)
21
+ name = name.to_s.downcase.to_sym
22
+ properties[name] = options
23
+
24
+ # Create a method for reading the property.
25
+ define_method(name) { read_property(name) }
26
+ end
27
+ end
28
+
29
+ # Returns the properties associated with this facebook
30
+ # object.
31
+ #
32
+ # @return [Hash]
33
+ def properties
34
+ @_properties ||= {}
35
+ end
36
+
37
+ # Propagates properties to another class. This is used
38
+ # internally for making sure that subclasses get all the
39
+ # properties associated with the parent classes.
40
+ def propagate_properties(klass)
41
+ properties.each { |n, o| klass.property(n, o) }
42
+ end
43
+ end
44
+
45
+ # Populates the properties with the given hash of data. This
46
+ # method also handles parsing non-primitive data such as other
47
+ # facebook objects.
48
+ def populate_properties(data)
49
+ property_values.clear
50
+
51
+ self.class.properties.keys.each do |name|
52
+ # Try both the string and symbol lookup to get the item
53
+ # associated with a key
54
+ item = data[name.to_s] || data [name]
55
+
56
+ if item.is_a?(Hash)
57
+ # For now, assume its a NamedObject. In the future, we'll be
58
+ # more careful.
59
+ item = NamedObject.new(item.merge(:_loaded => true))
60
+ end
61
+
62
+ property_values[name] = item
63
+ end
64
+
65
+ self
66
+ end
67
+
68
+ # Reads a property.
69
+ def read_property(name)
70
+ value = property_values[name]
71
+ value = value.name if value.is_a?(NamedObject)
72
+ value
73
+ end
74
+
75
+ # Stores the values of the variables properties
76
+ def property_values
77
+ @_property_values ||= {}
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,49 @@
1
+ module Fogli
2
+ class FacebookObject < FacebookGraph
3
+ # Includable module which has the scope methods to properly
4
+ # instantiate a {ConnectionScope}.
5
+ module ScopeMethods
6
+ # The various scoping types. Each method takes a single value
7
+ # associated with it, which is used to build up a new scope.
8
+ [:limit, :offset, :until, :since].each do |scope|
9
+ define_method(scope) do |value|
10
+ ConnectionScope.new(*_scope_initializer_args(scope, value))
11
+ end
12
+ end
13
+
14
+ # Scope the connection by the fields you're interested in. By
15
+ # default, the connection will load all the possible fields for
16
+ # the connection, which can result in a fairly large overhead if
17
+ # you're only interested in the `first_name`, for example.
18
+ #
19
+ # The parameter to this method can be almost anything which can
20
+ # intuitively be converted down to either a string or
21
+ # array. Some example uses follow (admittingly some of these are
22
+ # a bit pathological, but they're meant to showcase the
23
+ # flexibility of the method):
24
+ #
25
+ # fields("name,birthday,last_name")
26
+ # fields(:name, :birthday, :last_name)
27
+ # fields([:name, "birthday"], :last_name)
28
+ # fields("name,birthday", ["last_name"])
29
+ #
30
+ def fields(*fields)
31
+ ConnectionScope.new(*_scope_initializer_args(:fields, fields.flatten.join(",")))
32
+ end
33
+
34
+ # Returns the proper set of arguments to send to the
35
+ # {ConnectionScope} initializer given the scope and value.
36
+ #
37
+ # @param [Symbol] scope The scope option.
38
+ # @param [Object] value The value for the scope.
39
+ # @return [Array]
40
+ def _scope_initializer_args(scope, value)
41
+ if self.is_a?(ConnectionScope)
42
+ [proxy, options.merge(scope => value)]
43
+ else
44
+ [self, {scope => value}]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ module Fogli
2
+ class Group < FacebookObject
3
+ property :owner, :name, :description, :link, :venue, :privacy
4
+
5
+ connection :feed, :class => :Post
6
+ connection :members, :class => :User
7
+ end
8
+ end
data/lib/fogli/link.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Fogli
2
+ class Link < FacebookObject
3
+ property :from, :link, :name, :caption, :description,
4
+ :message
5
+
6
+ connection :comments, :class => :Comment
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Fogli
2
+ # A simple, named object. Many things in Facebook are associated
3
+ # with an ID but simply have a name. This object represents such an
4
+ # item.
5
+ class NamedObject < FacebookObject
6
+ property :name
7
+ end
8
+ end
data/lib/fogli/note.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Fogli
2
+ class Note < FacebookObject
3
+ property :from, :subject, :message, :created_time
4
+
5
+ connection :comments, :class => :Comment
6
+ end
7
+ end
@@ -0,0 +1,167 @@
1
+ require 'cgi'
2
+
3
+ module Fogli
4
+ # Handles OAuth authorization with Facebook to obtain an access key
5
+ # which can then be used to retrieve authorized data via the
6
+ # Facebook Open Graph API.
7
+ #
8
+ # # Requesting an Access Token
9
+ #
10
+ # Requesting an access token is a multi-step process:
11
+ #
12
+ # 1. Redirect the user to an authorization URL on the Facebook
13
+ # domain.
14
+ # 2. After authorizing, Facebook redirects the user back to your
15
+ # domain, with a verification string as the `code` GET parameter.
16
+ # 3. Request the access token from Facebook using the `code` GET
17
+ # parameter and the client ID and secret key given by Facebook
18
+ # during application creation.
19
+ #
20
+ # Fogli simplifies this into two separate API calls:
21
+ #
22
+ # 1. First call {authorize} to get the authorization URL.
23
+ # 2. Call {access_token} to get the access token.
24
+ #
25
+ # # Persisting an Access Token
26
+ #
27
+ # After retrieving an access token, it should be persisted into a
28
+ # cookie or some sort of session store. On subsequent requests, set
29
+ # the access token on the {Fogli} object:
30
+ #
31
+ # Fogli.access_token = cookies[:access_token]
32
+ #
33
+ # Fogli will then automatically use the access token on all API
34
+ # requests to retrieve authorized data.
35
+ #
36
+ # # Examples
37
+ #
38
+ # The following is a full length example based on
39
+ # [Sinatra](http://www.sinatrarb.com) syntax of authorizing a
40
+ # user. For examples on the wide variety of options that each method
41
+ # takes, please view {authorize} and {access_token}.
42
+ #
43
+ # require 'sinatra'
44
+ # reqiure 'fogli'
45
+ #
46
+ # # Set Fogli Configuration
47
+ # Fogli.client_id = "..."
48
+ # Fogli.client_secret = "..."
49
+ # Fogli.redirect_uri = "http://localhost:4567/my_email"
50
+ #
51
+ # get "/" do
52
+ # # Redirect user to authorization URL
53
+ # redirect_to Fogli::OAuth.authorize(:scope => "email")
54
+ # end
55
+ #
56
+ # get "/my_email" do
57
+ # # Store away the access token. In reality, this should be
58
+ # # persisted to some session storage (cookies, db, etc.)
59
+ # Fogli.access_token = Fogli::OAuth.access_token(:code => params[:code])
60
+ #
61
+ # # Output the current user's email
62
+ # User[:me].email
63
+ # end
64
+ #
65
+ class OAuth < FacebookGraph
66
+ AUTHORIZE_URI = "http://graph.facebook.com/oauth/authorize?client_id=%s&redirect_uri=%s"
67
+
68
+ extend Util::Options
69
+
70
+ # Returns the authorization URL to redirect the user to in order
71
+ # to get an access token. This method takes a hash as its sole
72
+ # argument, and has the following possible keys:
73
+ #
74
+ # * `:redirect_uri` (optional) - The URI to redirect to after
75
+ # the user authorizes. This URI will be given a single parameter
76
+ # `code` as a GET variable. This should be passed into
77
+ # {access_token} to finally retrieve the access token.
78
+ # * `:client_id` (optional) - The client ID of your Facebook
79
+ # application
80
+ # * `:scope` (optional) - Specifies additional [extended
81
+ # permissions](http://developers.facebook.com/docs/authentication/permissions)
82
+ # to request with the authorization.
83
+ #
84
+ # # Examples
85
+ #
86
+ # ## Requesting a Basic Authorize URL
87
+ #
88
+ # With options in the arguments:
89
+ #
90
+ # Fogli::OAuth.authorize(:client_id => "...",
91
+ # :redirect_uri => "...")
92
+ #
93
+ # With statically set options:
94
+ #
95
+ # Fogli.client_id = "..."
96
+ # Fogli.redirect_uri = "..."
97
+ # redirect_to Fogli::OAuth.authorize
98
+ #
99
+ # ## Requesting Extended Permissions
100
+ #
101
+ # Fogli::OAuth.authorize(:scope => ["user_email", "user_photos"])
102
+ #
103
+ # @param [Hash] options Described above.
104
+ # @return [String] The authorization URL to redirect the user to.
105
+ def self.authorize(options=nil)
106
+ defaults = {
107
+ :client_id => Fogli.client_id,
108
+ :redirect_uri => Fogli.redirect_uri,
109
+ }
110
+
111
+ options = verify_options(options, {
112
+ :required_keys => defaults.keys,
113
+ :valid_keys => [defaults.keys, :scope].flatten,
114
+ :default => defaults
115
+ })
116
+ result = AUTHORIZE_URI % [options[:client_id], CGI.escape(options[:redirect_uri])]
117
+ if options[:scope]
118
+ options[:scope] = [options[:scope]] if !options.is_a?(Array)
119
+ result += "&scope=#{options[:scope].join(",")}"
120
+ end
121
+
122
+ result
123
+ end
124
+
125
+ # Exchanges a verfication code for an access token. After
126
+ # redirecting a user to the {authorize} URL, the user is
127
+ # redirected back with the `code` GET parameter. This parameter
128
+ # along with the `redirect_uri` should get passed into this
129
+ # method, which will then return the access token. The possible
130
+ # keys for the arguments hash are as follows:
131
+ #
132
+ # * `:redirect_uri` (**required**) - The URI which the user was
133
+ # redirected to with the `code` param.
134
+ # * `:code` (**required**) - The code which was given as a GET
135
+ # param by facebook.
136
+ # * `:client_id` (optional) - The client ID of your facebook
137
+ # application. If this is not specified here, it must be set via
138
+ # `Fogli.client_id=`
139
+ # * `:client_secret` (optional) - The client secret key of your
140
+ # facebook application. If this is not specified here, it must be
141
+ # set via `Fogli.client_secret=`.
142
+ #
143
+ # The return value is the access token. This token should be
144
+ # persisted in a cookie or some other session store, so it can be
145
+ # used on subsequent requests. The token can be set via
146
+ # `Fogli.access_token=`, which will cause all API calls to use the
147
+ # specified access token.
148
+ #
149
+ # @param [Hash] options Described above.
150
+ # @return [String] Access token
151
+ def self.access_token(options)
152
+ defaults = {
153
+ :client_id => Fogli.client_id,
154
+ :client_secret => Fogli.client_secret,
155
+ :redirect_uri => Fogli.redirect_uri,
156
+ :code => nil
157
+ }
158
+
159
+ options = verify_options(options, {
160
+ :required_keys => defaults.keys,
161
+ :default => defaults
162
+ })
163
+ result = get("/oauth/access_token", options)
164
+ result.split(/(&|=)/)[2]
165
+ end
166
+ end
167
+ end
data/lib/fogli/page.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Fogli
2
+ class Page < FacebookObject
3
+ property :name, :category
4
+
5
+ connection :feed, :tagged, :posts, :class => :Post
6
+ connection :links, :class => :Link
7
+ connection :photos, :class => :Photo
8
+ connection :groups, :class => :Group
9
+ connection :albums, :class => :Album
10
+ connection :statuses, :class => :Status
11
+ connection :videos, :class => :Video
12
+ connection :notes, :class => :Note
13
+ connection :events, :class => :Event
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module Fogli
2
+ class Photo < FacebookObject
3
+ property :from, :tags, :name, :source, :height, :width,
4
+ :link, :created_time
5
+
6
+ connection :comments, :class => :Comment
7
+ end
8
+ end
data/lib/fogli/post.rb ADDED
@@ -0,0 +1,18 @@
1
+ module Fogli
2
+ class Post < FacebookObject
3
+ property :from, :to, :message, :link, :name, :caption, :description, :type,
4
+ :source, :icon, :attribution, :actions, :likes, :created_time
5
+
6
+ connection :comments, :class => :Comment
7
+
8
+ # Like a post. This method requires authorization.
9
+ def like!
10
+ post("/likes")
11
+ end
12
+
13
+ # Unlike a post. This method requires authorization.
14
+ def unlike!
15
+ post("/likes", :method => :delete)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Fogli
2
+ class Status < FacebookObject
3
+ property :from, :message
4
+
5
+ connection :comments, :class => :Comment
6
+ end
7
+ end
data/lib/fogli/user.rb ADDED
@@ -0,0 +1,36 @@
1
+ module Fogli
2
+ class User < FacebookObject
3
+ property :first_name, :last_name, :name, :link, :about,
4
+ :birthday, :work, :education, :email, :website,
5
+ :hometown, :location, :gender, :interested_in,
6
+ :meeting_for, :relationship_status, :religion,
7
+ :political, :verified, :significant_other, :timezone
8
+
9
+ connection :friends, :class => :User
10
+ connection :home, :feed, :tagged, :posts, :class => :Post
11
+ connection :activities, :interests, :music, :books, :movies,
12
+ :television, :class => :CategorizedObject
13
+ connection :likes, :class => :Page
14
+ connection :photos, :class => :Photo
15
+ connection :albums, :class => :Album
16
+ connection :videos, :class => :Video
17
+ connection :groups, :class => :Group
18
+ connection :statuses, :class => :Status
19
+ connection :links, :class => :Link
20
+ connection :notes, :class => :Note
21
+ connection :events, :class => :Event
22
+
23
+ # Checks if a user is authorized. This returns a true or false
24
+ # depending if we're allowed to make authorized calls yet. If this
25
+ # returns false, the user must be authorized using the {OAuth}
26
+ # class via a simple two step process.
27
+ #
28
+ # @return [Boolean]
29
+ def self.authorized?
30
+ User.find(:me, :fields => :id).load!
31
+ true
32
+ rescue Fogli::Exception
33
+ false
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,61 @@
1
+ # Taken directly from Rails 3 core. I claim no authorship of this
2
+ # code. Fogli first attempts to load the actual ActiveSupport code
3
+ # if it exists, but falls back to this implementation. This way,
4
+ # running Fogli with rails shouldn't conflict in any way.
5
+
6
+ #
7
+ # Extends the module object with module and instance accessors for class attributes,
8
+ # just like the native attr* accessors for instance attributes.
9
+ #
10
+ # module AppConfiguration
11
+ # mattr_accessor :google_api_key
12
+ # self.google_api_key = "123456789"
13
+ #
14
+ # mattr_accessor :paypal_url
15
+ # self.paypal_url = "www.sandbox.paypal.com"
16
+ # end
17
+ #
18
+ # AppConfiguration.google_api_key = "overriding the api key!"
19
+ class Module
20
+ def mattr_reader(*syms)
21
+ syms.each do |sym|
22
+ next if sym.is_a?(Hash)
23
+ class_eval(<<-EOS, __FILE__, __LINE__)
24
+ unless defined? @@#{sym}
25
+ @@#{sym} = nil
26
+ end
27
+
28
+ def self.#{sym}
29
+ @@#{sym}
30
+ end
31
+
32
+ def #{sym}
33
+ @@#{sym}
34
+ end
35
+ EOS
36
+ end
37
+ end
38
+
39
+ def mattr_writer(*syms)
40
+ syms.each do |sym|
41
+ class_eval(<<-EOS, __FILE__, __LINE__)
42
+ unless defined? @@#{sym}
43
+ @@#{sym} = nil
44
+ end
45
+
46
+ def self.#{sym}=(obj)
47
+ @@#{sym} = obj
48
+ end
49
+
50
+ def #{sym}=(obj)
51
+ @@#{sym} = obj
52
+ end
53
+ EOS
54
+ end
55
+ end
56
+
57
+ def mattr_accessor(*syms)
58
+ mattr_reader(*syms)
59
+ mattr_writer(*syms)
60
+ end
61
+ end