open_graph 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.gitmodules +3 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/Readme.md +71 -0
- data/lib/open_graph.rb +38 -0
- data/lib/open_graph/fb_open_graph.rb +192 -0
- data/lib/open_graph/rails/action_controller_accessor.rb +9 -0
- data/lib/open_graph/rails/action_view_help.rb +18 -0
- data/lib/open_graph/test/facebook_stub.rb +97 -0
- data/lib/open_graph/test/facebook_stub/picture.png +0 -0
- data/lib/open_graph/version.rb +3 -0
- data/open_graph.gemspec +21 -0
- metadata +80 -0
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# OpenGraph #
|
2
|
+
|
3
|
+
A ruby wrapper for the new [facebook graph api](http://developers.facebook.com/docs/reference/api/)
|
4
|
+
|
5
|
+
# Ruby #
|
6
|
+
|
7
|
+
## Configuration ##
|
8
|
+
|
9
|
+
Gemfile
|
10
|
+
|
11
|
+
gem 'open_graph'
|
12
|
+
|
13
|
+
config/facebook.yml
|
14
|
+
|
15
|
+
development:
|
16
|
+
appid: YOUR_APP_ID
|
17
|
+
key: YOUR_APP_KEY
|
18
|
+
secret: YOUR_APP_SECRET
|
19
|
+
production:
|
20
|
+
...
|
21
|
+
|
22
|
+
You'll have access to the config by `OpenGraph.config[:key]`
|
23
|
+
|
24
|
+
## Initialization ##
|
25
|
+
|
26
|
+
ApplicationController::Base has access to `fb_open_graph`
|
27
|
+
|
28
|
+
`fb_open_graph` automatically parses and validates your facebook cookie like a boss.
|
29
|
+
|
30
|
+
## API ##
|
31
|
+
|
32
|
+
fb_open_graph.valid?
|
33
|
+
will return true or false if you have a valid session.
|
34
|
+
|
35
|
+
fb_open_graph.use_manual_session!(new_facebook_id, new_access_token)
|
36
|
+
will force it to not use the cookie, but use your facebook\_id and\_access\_token instead
|
37
|
+
|
38
|
+
### Get Calls ###
|
39
|
+
|
40
|
+
All get calls take an options hash, but not all get calls will use them. The calls which accept paging can take (:limit, :offset, :until, and :since).
|
41
|
+
|
42
|
+
fb_open_graph.info
|
43
|
+
will return a hash of information. possible keys may be `[:name, :email, :first_name, :middle_name, :last_name, :gender, :id, :locale :religion, :bio, :hometown, :birthday]`, etc. info[:location] returns a hash of `[:name, :city, :zip, :country, :id, :state]`
|
44
|
+
|
45
|
+
fb_open_graph.picture(:type => (square|small|large))
|
46
|
+
will return the a hash of `{:content_type => 'image/jpeg', :image => actual_image_data}`.
|
47
|
+
|
48
|
+
#### Other Get calls ####
|
49
|
+
friends, news_feed, profile_feed, likes, movies, music, books, notes, photo_tags, photo_albums, video_tags, video_uploads, events, groups, checkins
|
50
|
+
|
51
|
+
### Publish Calls ###
|
52
|
+
**UNDER CONSTRUCTION FOR BETTER PUBLIC API**
|
53
|
+
|
54
|
+
All methods take options specified in the facebook api such as [Post](http://developers.facebook.com/docs/reference/api/post/#publishing)
|
55
|
+
|
56
|
+
publish!, note!, link!, event!, album!, checkin!
|
57
|
+
|
58
|
+
### Permissions ###
|
59
|
+
|
60
|
+
fb_open_graph.has_permissions?(*permission_names_comma_separated)
|
61
|
+
will return true or false
|
62
|
+
|
63
|
+
# JavaScript #
|
64
|
+
|
65
|
+
You'll want something that looks like this in your view:
|
66
|
+
|
67
|
+
if Rails.env.test?
|
68
|
+
include_facebook_stub
|
69
|
+
else
|
70
|
+
javascript_include_tag '//connect.facebook.net/en_US/all.js#xfbml=1'
|
71
|
+
end
|
data/lib/open_graph.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require "digest/md5"
|
3
|
+
require 'open_graph/rails/action_controller_accessor'
|
4
|
+
require 'open_graph/rails/action_view_help'
|
5
|
+
require 'open_graph/fb_open_graph'
|
6
|
+
|
7
|
+
if defined?(RAILS_ENV) && RAILS_ENV == 'test'
|
8
|
+
require 'open_graph/test/facebook_stub'
|
9
|
+
end
|
10
|
+
|
11
|
+
module OpenGraph
|
12
|
+
def config
|
13
|
+
@config ||= begin
|
14
|
+
config = HashWithIndifferentAccess.new
|
15
|
+
yaml_file = YAML.load_file("#{RAILS_ROOT}/config/facebook.yml")
|
16
|
+
config.merge(yaml_file[RAILS_ENV])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def logger
|
21
|
+
@logger ||= begin
|
22
|
+
if defined?(::Rails.logger)
|
23
|
+
::Rails.logger
|
24
|
+
elsif defined?(RAILS_DEFAULT_LOGGER)
|
25
|
+
RAILS_DEFAULT_LOGGER
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
module_function :config, :logger
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
if defined?(ActionController::Base)
|
34
|
+
ActionController::Base.send(:include, OpenGraph::Rails::ActionControllerAccessor)
|
35
|
+
end
|
36
|
+
if defined?(ActionView::Base)
|
37
|
+
ActionView::Base.send(:include, OpenGraph::Rails::ActionViewHelp)
|
38
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module OpenGraph
|
2
|
+
class FBOpenGraph < Struct.new(:all_cookies)
|
3
|
+
|
4
|
+
def facebook_cookie
|
5
|
+
@facebook_cookie ||= begin
|
6
|
+
cookie = all_cookies["fbs_#{OpenGraph.config[:appid]}"]
|
7
|
+
validated_cookie(cookie)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
access_token.present?
|
13
|
+
end
|
14
|
+
|
15
|
+
def use_manual_session!(uid, access_token)
|
16
|
+
@uid = uid
|
17
|
+
@access_token = access_token
|
18
|
+
@manual = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def access_token
|
22
|
+
@access_token ||= facebook_cookie['access_token'] unless @manual
|
23
|
+
@access_token || ''
|
24
|
+
end
|
25
|
+
|
26
|
+
def uid
|
27
|
+
@uid ||= facebook_cookie['uid'] unless @manual
|
28
|
+
@uid.blank? ? nil : @uid.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
# creates .news_feed(options)
|
32
|
+
# options for pagable_method
|
33
|
+
# :limit, :offset, :until, :since
|
34
|
+
# options for picture
|
35
|
+
# :type => (square|small|large)
|
36
|
+
# begin get methods
|
37
|
+
GRAPH_GET_METHODS = {
|
38
|
+
:info => nil,
|
39
|
+
:friends => :friends,
|
40
|
+
:news_feed => :home,
|
41
|
+
:profile_feed => :feed,
|
42
|
+
:likes => :likes,
|
43
|
+
:movies => :movies,
|
44
|
+
:music => :music,
|
45
|
+
:books => :books,
|
46
|
+
:notes => :notes,
|
47
|
+
:photo_tags => :photos,
|
48
|
+
:photo_albums => :albums,
|
49
|
+
:video_tags => :videos,
|
50
|
+
:video_uploads => :'videos/uploaded',
|
51
|
+
:events => :events,
|
52
|
+
:groups => :groups,
|
53
|
+
:checkins => :checkins,
|
54
|
+
:picture => :picture
|
55
|
+
}
|
56
|
+
defines = ""
|
57
|
+
GRAPH_GET_METHODS.each_pair do |method, connection_type|
|
58
|
+
defines << <<-METHOD
|
59
|
+
def #{method}(options={})
|
60
|
+
graph_get(options.merge(:connection_type => #{connection_type.inspect}, :requested_call => #{method.inspect}))
|
61
|
+
end
|
62
|
+
METHOD
|
63
|
+
end
|
64
|
+
eval(defines)
|
65
|
+
|
66
|
+
# end get methods
|
67
|
+
GRAPH_PUBLISH_METHODS = {
|
68
|
+
# profiles
|
69
|
+
:publish! => :feed,
|
70
|
+
:note! => :notes,
|
71
|
+
:link! => :links,
|
72
|
+
:event! => :events,
|
73
|
+
:album! => :albums,
|
74
|
+
:checkin! => :checkins,
|
75
|
+
}
|
76
|
+
GRAPH_PUBLISH_METHODS.each_pair do |method, connection_type|
|
77
|
+
defines << <<-METHOD
|
78
|
+
def #{method}(options={})
|
79
|
+
graph_post(options.merge(:connection_type => #{connection_type.inspect}))
|
80
|
+
end
|
81
|
+
METHOD
|
82
|
+
end
|
83
|
+
eval(defines)
|
84
|
+
|
85
|
+
def fql_query(table, field_names, new_uid = uid)
|
86
|
+
field_names = field_names.join(', ') if field_names.is_a?(Array)
|
87
|
+
query = "SELECT #{field_names} FROM #{table} WHERE uid = #{new_uid}"
|
88
|
+
fql_get(query)
|
89
|
+
end
|
90
|
+
|
91
|
+
def has_permissions?(permission_names)
|
92
|
+
response = fql_query('permissions', permission_names)
|
93
|
+
permission_hash = response.first
|
94
|
+
return false unless permission_hash.is_a?(Hash)
|
95
|
+
|
96
|
+
permission_names = permission_names.split(',').collect(&:strip) if permission_names.is_a?(String)
|
97
|
+
permission_names.each do |perm|
|
98
|
+
return false if permission_hash[perm] != 1
|
99
|
+
end
|
100
|
+
return true
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def validated_cookie(cookie)
|
106
|
+
cookie = cookie.present? ? Rack::Utils.parse_query(cookie.gsub('"', '')) : nil
|
107
|
+
return {} unless cookie.present?
|
108
|
+
cookieness = cookie.except('sig').keys.sort.collect {|k| "#{k}=#{cookie[k]}"}.join
|
109
|
+
cookieness += OpenGraph.config[:secret]
|
110
|
+
calculated_sig = Digest::MD5.hexdigest(cookieness)
|
111
|
+
calculated_sig == cookie['sig'] ? cookie : {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def fql_get(query)
|
115
|
+
options = {
|
116
|
+
:access_token => access_token,
|
117
|
+
:query => query,
|
118
|
+
:format => 'json'
|
119
|
+
}
|
120
|
+
HTTParty.get("https://api.facebook.com/method/fql.query#{query_string(options)}").parsed_response
|
121
|
+
end
|
122
|
+
|
123
|
+
def graph_post(options={})
|
124
|
+
return false unless valid?
|
125
|
+
options.symbolize_keys!.reverse_merge!(default_options)
|
126
|
+
url = build_url(options.dup)
|
127
|
+
HTTParty.post(url).parsed_response
|
128
|
+
end
|
129
|
+
|
130
|
+
def fb_cache(key, value=nil)
|
131
|
+
@cache ||= {}
|
132
|
+
if(value)
|
133
|
+
@cache[key] = value
|
134
|
+
else
|
135
|
+
@cache[key]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# options are :connection_type, :uid, :access_token
|
140
|
+
# + any facebook options
|
141
|
+
def graph_get(options={})
|
142
|
+
options.symbolize_keys!.reverse_merge!(default_options)
|
143
|
+
requested_call = options.delete(:requested_call)
|
144
|
+
url = build_url(options.dup)
|
145
|
+
|
146
|
+
return fb_cache(url) if fb_cache(url)
|
147
|
+
|
148
|
+
OpenGraph.logger.info("[OpenGraph] fetching: #{url}")
|
149
|
+
response = HTTParty.get(url).response
|
150
|
+
response_hash = parse_response(response)
|
151
|
+
if response.is_a?(Net::HTTPOK)
|
152
|
+
response_hash['location'] = fetch_codified_location(options.slice(:uid, :access_token)) if requested_call == :info
|
153
|
+
end
|
154
|
+
fb_cache(url, response_hash)
|
155
|
+
end
|
156
|
+
|
157
|
+
def default_options
|
158
|
+
{:uid => uid, :access_token => access_token}
|
159
|
+
end
|
160
|
+
|
161
|
+
def build_url(options)
|
162
|
+
url = "https://graph.facebook.com/#{options.delete(:uid)}"
|
163
|
+
if connection_type = options.delete(:connection_type)
|
164
|
+
url << "/#{connection_type}"
|
165
|
+
end
|
166
|
+
url << query_string(options)
|
167
|
+
url
|
168
|
+
end
|
169
|
+
|
170
|
+
def query_string(options)
|
171
|
+
options.present? ? "?#{options.collect {|k, v| "#{k}=#{CGI.escape(v)}"}.join('&')}" : ""
|
172
|
+
end
|
173
|
+
|
174
|
+
#Craptacular way to get location of user (deprecated FB call to old api) REPLACE AT SOME POINT!
|
175
|
+
def fetch_codified_location(options_for_fql)
|
176
|
+
OpenGraph.logger.info("[OpenGraph] DEPRICATED - fetching codified location")
|
177
|
+
fql_query('user', 'current_location').first['current_location']
|
178
|
+
end
|
179
|
+
|
180
|
+
def parse_response(response)
|
181
|
+
case response.content_type
|
182
|
+
when 'text/javascript'
|
183
|
+
JSON.parse(response.body)
|
184
|
+
when 'image/jpeg'
|
185
|
+
{:content_type => response.content_type, :image => response.body}
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module OpenGraph
|
2
|
+
module Rails
|
3
|
+
module ActionViewHelp
|
4
|
+
@@facebook_javascript_stub = nil
|
5
|
+
def include_facebook_stub
|
6
|
+
@@facebook_javascript_stub ||= begin
|
7
|
+
data = ''
|
8
|
+
f =File.open(File.join( File.dirname(__FILE__),"../../../", "facebook-stub/", "facebook-stub.js"))
|
9
|
+
f.each_line do |line|
|
10
|
+
data << line
|
11
|
+
end
|
12
|
+
f.close
|
13
|
+
javascript_tag data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module OpenGraph
|
2
|
+
class FBOpenGraph
|
3
|
+
@@access_token = nil
|
4
|
+
@@uid = nil
|
5
|
+
|
6
|
+
BASE_INFO = {
|
7
|
+
"name" => "Gandalf The Grey",
|
8
|
+
"location" => {
|
9
|
+
"name" => "San Francisco, California",
|
10
|
+
"city" => "San Francisco",
|
11
|
+
"zip" => "",
|
12
|
+
"country" => "United States",
|
13
|
+
"id" => 114952118516947,
|
14
|
+
"state" => "California"
|
15
|
+
},
|
16
|
+
"timezone" => -8,
|
17
|
+
"gender" => "male",
|
18
|
+
"id" => "6013893",
|
19
|
+
"birthday" => "01/12/1987",
|
20
|
+
"last_name" => "Shafer",
|
21
|
+
"updated_time" => "2011-02-10T00:40:26+0000",
|
22
|
+
"verified" => true,
|
23
|
+
"locale" => "en_US",
|
24
|
+
"religion" => "Wikipedia",
|
25
|
+
"bio" => "The greatest thing you'll ever learn is just to love and be loved in return.\r\n\r\nIf hearing the name Sharon Salinger causes you physical pain and emotional trauma then we are friends.\r\n\r\nMy life has a great cast but I can't figure out the plot.",
|
26
|
+
"hometown" => { "name" => nil, "id" => "" },
|
27
|
+
"link" => "http://www.facebook.com/thomas.shafer",
|
28
|
+
"political" => "Liberal",
|
29
|
+
"email" => "gandalf@change.org",
|
30
|
+
"middle_name" => "'Growly Pants'",
|
31
|
+
"first_name" => "Thomas"
|
32
|
+
}
|
33
|
+
|
34
|
+
# class level methods for setting the responses you would like to get
|
35
|
+
class << self
|
36
|
+
def info_override!(key, value)
|
37
|
+
BASE_INFO[key.to_s] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
def invalid!
|
41
|
+
@@access_token = ''
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid!
|
45
|
+
@@access_token = 'fb_token'
|
46
|
+
end
|
47
|
+
|
48
|
+
def uid=(uid)
|
49
|
+
@@uid = uid
|
50
|
+
end
|
51
|
+
|
52
|
+
def access_token=(token)
|
53
|
+
@@access_token = token
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def uid_with_testing
|
58
|
+
@@uid || uid_without_testing
|
59
|
+
end
|
60
|
+
alias_method_chain :uid, :testing
|
61
|
+
|
62
|
+
def access_token_with_testing
|
63
|
+
@@access_token || access_token_without_testing
|
64
|
+
end
|
65
|
+
alias_method_chain :access_token, :testing
|
66
|
+
|
67
|
+
defines = ''
|
68
|
+
GRAPH_GET_METHODS.merge(GRAPH_PUBLISH_METHODS).keys.send('-', [:friends, :info, :picture]).each do |method|
|
69
|
+
defines << <<-METHOD
|
70
|
+
def #{method}(*args)
|
71
|
+
raise "#{method} not stubbed!!"
|
72
|
+
end
|
73
|
+
METHOD
|
74
|
+
end
|
75
|
+
eval(defines)
|
76
|
+
|
77
|
+
def friends(*args)
|
78
|
+
return false unless valid?
|
79
|
+
{"data"=>
|
80
|
+
[{"name"=>"Michael Levin-Gesundheit", "id"=>"11160"},
|
81
|
+
{"name"=>"Eleanor Birrell", "id"=>"21139"},
|
82
|
+
{"name"=>"Clotilde Dedecker", "id"=>"24021"},
|
83
|
+
{"name"=>"Nancy Elizabeth Townsend", "id"=>"100001899260534"},
|
84
|
+
{"name"=>"Allba Zackson", "id"=>"100001938274584"}]}
|
85
|
+
end
|
86
|
+
|
87
|
+
def info(*args)
|
88
|
+
return false unless valid?
|
89
|
+
BASE_INFO
|
90
|
+
end
|
91
|
+
|
92
|
+
def picture(*args)
|
93
|
+
@picture_data ||= {:content_type => 'image/png', :image => File.read(File.dirname(__FILE__)+'/facebook_stub/picture.png')}
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
Binary file
|
data/open_graph.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "open_graph/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "open_graph"
|
7
|
+
s.version = OpenGraph::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Change.org Engineering"]
|
10
|
+
s.email = ["engineering@change.org"]
|
11
|
+
s.homepage = "https://github.com/change/open_graph"
|
12
|
+
s.summary = %q{A ruby wrapper for the new facebook graph api}
|
13
|
+
s.description = %q{OpenGraph provides an easy to use wrapper for making facebook calls. It also provides javascript stubs allowing seemless testing against the javascript sdk.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "open_graph"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: open_graph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Change.org Engineering
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-23 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: OpenGraph provides an easy to use wrapper for making facebook calls. It also provides javascript stubs allowing seemless testing against the javascript sdk.
|
23
|
+
email:
|
24
|
+
- engineering@change.org
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- .gitmodules
|
34
|
+
- Gemfile
|
35
|
+
- Rakefile
|
36
|
+
- Readme.md
|
37
|
+
- lib/open_graph.rb
|
38
|
+
- lib/open_graph/fb_open_graph.rb
|
39
|
+
- lib/open_graph/rails/action_controller_accessor.rb
|
40
|
+
- lib/open_graph/rails/action_view_help.rb
|
41
|
+
- lib/open_graph/test/facebook_stub.rb
|
42
|
+
- lib/open_graph/test/facebook_stub/picture.png
|
43
|
+
- lib/open_graph/version.rb
|
44
|
+
- open_graph.gemspec
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: https://github.com/change/open_graph
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: open_graph
|
75
|
+
rubygems_version: 1.5.0
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A ruby wrapper for the new facebook graph api
|
79
|
+
test_files: []
|
80
|
+
|