fogli 0.1.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 (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