fb_graph 2.1.7 → 2.1.8

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 (34) hide show
  1. data/VERSION +1 -1
  2. data/lib/fb_graph.rb +8 -2
  3. data/lib/fb_graph/achievement.rb +44 -0
  4. data/lib/fb_graph/ad_account.rb +2 -0
  5. data/lib/fb_graph/ad_connection_object.rb +38 -0
  6. data/lib/fb_graph/application.rb +27 -6
  7. data/lib/fb_graph/broad_targeting_category.rb +13 -0
  8. data/lib/fb_graph/connections/achievements.rb +21 -0
  9. data/lib/fb_graph/connections/ad_connection_objects.rb +14 -0
  10. data/lib/fb_graph/connections/broad_targeting_categories.rb +14 -0
  11. data/lib/fb_graph/connections/mutual_friends.rb +22 -0
  12. data/lib/fb_graph/connections/user_achievements.rb +22 -0
  13. data/lib/fb_graph/exception.rb +41 -1
  14. data/lib/fb_graph/node.rb +2 -11
  15. data/lib/fb_graph/user.rb +2 -0
  16. data/lib/fb_graph/user_achievement.rb +35 -0
  17. data/spec/fb_graph/achievement_spec.rb +59 -0
  18. data/spec/fb_graph/ad_connection_object_spec.rb +30 -0
  19. data/spec/fb_graph/application_spec.rb +3 -1
  20. data/spec/fb_graph/broad_targeting_category_spec.rb +17 -0
  21. data/spec/fb_graph/connections/achievements_spec.rb +27 -0
  22. data/spec/fb_graph/connections/ad_connection_objects_spec.rb +20 -0
  23. data/spec/fb_graph/connections/broad_targeting_categories_spec.rb +18 -0
  24. data/spec/fb_graph/connections/mutual_friends_spec.rb +28 -0
  25. data/spec/fb_graph/connections/user_achievements_spec.rb +37 -0
  26. data/spec/fb_graph/exception_spec.rb +97 -1
  27. data/spec/fb_graph/user_achievement_spec.rb +53 -0
  28. data/spec/mock_json/ad_accounts/ad_connection_objects/test_connection_objects.json +34 -0
  29. data/spec/mock_json/ad_accounts/broad_targeting_categories/test_bct.json +19 -0
  30. data/spec/mock_json/applications/achievements/sample.json +24 -0
  31. data/spec/mock_json/users/mutual_friends/me_and_agektmr.json +126 -0
  32. data/spec/mock_json/users/user_achievements/created.json +1 -0
  33. data/spec/mock_json/users/user_achievements/sample.json +31 -0
  34. metadata +41 -2
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.7
1
+ 2.1.8
@@ -61,14 +61,17 @@ require 'fb_graph/venue'
61
61
  require 'fb_graph/work'
62
62
 
63
63
  require 'fb_graph/node'
64
+ require 'fb_graph/achievement'
64
65
  require 'fb_graph/ad_account'
65
66
  require 'fb_graph/ad_campaign'
66
67
  require 'fb_graph/ad_campaign_stat'
68
+ require 'fb_graph/ad_connection_object'
67
69
  require 'fb_graph/ad_group'
68
70
  require 'fb_graph/ad_group_stat'
69
71
  require 'fb_graph/ad_keyword'
70
72
  require 'fb_graph/ad_keyword_suggestion'
71
73
  require 'fb_graph/ad_keyword_valid'
74
+ require 'fb_graph/broad_targeting_category'
72
75
  require 'fb_graph/reach_estimate.rb'
73
76
  require 'fb_graph/album'
74
77
  require 'fb_graph/app_request'
@@ -98,10 +101,13 @@ require 'fb_graph/tab'
98
101
  require 'fb_graph/tag'
99
102
  require 'fb_graph/thread'
100
103
  require 'fb_graph/user'
101
- require 'fb_graph/ad_user'
102
- require 'fb_graph/test_user' # Load after FbGraph::User
104
+ require 'fb_graph/user_achievement'
103
105
  require 'fb_graph/video'
104
106
 
107
+ # Load after FbGraph::User
108
+ require 'fb_graph/ad_user'
109
+ require 'fb_graph/test_user'
110
+
105
111
  require 'fb_graph/klass'
106
112
  require 'fb_graph/project'
107
113
 
@@ -0,0 +1,44 @@
1
+ module FbGraph
2
+ class Achievement < Node
3
+ attr_accessor :type, :application, :title, :url, :description, :image, :images, :data, :updated_time, :context, :points, :display_order
4
+
5
+ def initialize(identifier, attributes = {})
6
+ super
7
+
8
+ # TODO: Handle data, context in smarter way.
9
+ [:type, :title, :url, :description, :data, :context].each do |key|
10
+ send "#{key}=", attributes[key]
11
+ end
12
+
13
+ if self.data
14
+ @points = self.data[:points]
15
+ end
16
+
17
+ if self.context
18
+ @display_order = self.context[:display_order]
19
+ end
20
+
21
+ @images = []
22
+ if _images_ = attributes[:image]
23
+ _images_.each do |_image_|
24
+ @images << _image_[:url]
25
+ end
26
+ end
27
+ @image = @images.first
28
+
29
+ if application = attributes[:application]
30
+ application[:link] = application[:url] # for some reason, FB uses "url" only here..
31
+ @application = Application.new(application[:id], application)
32
+ end
33
+
34
+ if attributes[:updated_time]
35
+ @updated_time = Time.parse(attributes[:updated_time]).utc
36
+ end
37
+ end
38
+
39
+ def destroy(options = {})
40
+ options[:access_token] ||= self.access_token
41
+ application.unregister_achievement!(url, options)
42
+ end
43
+ end
44
+ end
@@ -4,7 +4,9 @@ module FbGraph
4
4
  include Connections::AdGroups
5
5
  include Connections::AdCampaignStats
6
6
  include Connections::AdGroupStats
7
+ include Connections::BroadTargetingCategories
7
8
  include Connections::ReachEstimates
9
+ include Connections::AdConnectionObjects
8
10
 
9
11
  attr_accessor :account_id, :name, :account_status, :daily_spend_limit, :users, :currency, :timezone_id, :timezone_name, :capabilities, :account_groups
10
12
 
@@ -0,0 +1,38 @@
1
+ module FbGraph
2
+ class AdConnectionObject < Node
3
+ attr_accessor :name, :url, :type, :tabs, :picture, :object
4
+
5
+ TYPES = {
6
+ :page => 1,
7
+ :application => 2,
8
+ :event => 3,
9
+ :place => 6,
10
+ :domain => 7
11
+ }
12
+
13
+ def initialize(identifier, attributes = {})
14
+ super
15
+
16
+ %w(name url type tabs picture).each do |field|
17
+ send("#{field}=", attributes[field.to_sym])
18
+ end
19
+
20
+ self.object = if page?
21
+ FbGraph::Page.new(identifier)
22
+ elsif application?
23
+ FbGraph::Application.new(identifier)
24
+ elsif event?
25
+ FbGraph::Event.new(identifier)
26
+ elsif place?
27
+ FbGraph::Place.new(identifier)
28
+ elsif domain?
29
+ FbGraph::Domain.new(identifier)
30
+ end
31
+ end
32
+
33
+ # Defines methods for page?, application?, event? and so forth
34
+ TYPES.keys.each do |object_type|
35
+ define_method("#{object_type}?") { type == TYPES[object_type] }
36
+ end
37
+ end
38
+ end
@@ -1,6 +1,7 @@
1
1
  module FbGraph
2
2
  class Application < Node
3
3
  include Connections::Accounts
4
+ include Connections::Achievements
4
5
  include Connections::Albums
5
6
  include Connections::Events
6
7
  include Connections::Feed
@@ -20,15 +21,35 @@ module FbGraph
20
21
  # include Connections::Translations
21
22
  include Connections::Videos
22
23
 
23
- attr_accessor :name, :description, :category, :link, :secret
24
+ @@attributes = [
25
+ :name,
26
+ :description,
27
+ :canvas_name,
28
+ :category,
29
+ :company,
30
+ :icon_url,
31
+ :subcategory,
32
+ :link,
33
+ :logo_url,
34
+ :daily_active_users,
35
+ :weekly_active_users,
36
+ :monthly_active_users,
37
+ :secret
38
+ ]
39
+ attr_accessor *@@attributes
24
40
 
25
41
  def initialize(client_id, attributes = {})
26
42
  super
27
- @name = attributes[:name]
28
- @description = attributes[:description]
29
- @category = attributes[:category]
30
- @link = attributes[:link]
31
- @secret = attributes[:secret]
43
+ @@attributes.each do |key|
44
+ # NOTE:
45
+ # For some reason, Graph API returns daily_active_users, weekly_active_users, monthly_active_users as JSON string.
46
+ value = if [:daily_active_users, :weekly_active_users, :monthly_active_users].include?(key)
47
+ attributes[key].to_i
48
+ else
49
+ attributes[key]
50
+ end
51
+ self.send("#{key}=", value)
52
+ end
32
53
  end
33
54
 
34
55
  def get_access_token(secret = nil)
@@ -0,0 +1,13 @@
1
+ module FbGraph
2
+ class BroadTargetingCategory < Node
3
+ attr_accessor :name, :parent_category
4
+
5
+ def initialize(identifier, attributes = {})
6
+ super
7
+
8
+ %w(name parent_category).each do |field|
9
+ self.send("#{field}=", attributes[field.to_sym])
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module FbGraph
2
+ module Connections
3
+ module Achievements
4
+ def achievements(options = {})
5
+ options[:access_token] ||= self.access_token
6
+ achievements = self.connection(:achievements, options)
7
+ achievements.map! do |achievement|
8
+ Achievement.new(achievement[:id], achievement.merge(:access_token => options[:access_token]))
9
+ end
10
+ end
11
+
12
+ def achievement!(achievement_url, options = {})
13
+ post options.merge(:connection => :achievements, :achievement => achievement_url)
14
+ end
15
+
16
+ def unregister_achievement!(achievement_url, options = {})
17
+ delete options.merge(:connection => :achievements, :achievement => achievement_url)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module FbGraph
2
+ module Connections
3
+ module AdConnectionObjects
4
+ def connection_objects(options = {})
5
+ connection_objects = self.connection(:connectionobjects, options)
6
+ connection_objects.map! do |connection_object|
7
+ AdConnectionObject.new(connection_object[:id], connection_object.merge(
8
+ :access_token => options[:access_token] || self.access_token
9
+ ))
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module FbGraph
2
+ module Connections
3
+ module BroadTargetingCategories
4
+ def broad_targeting_categories(options = {})
5
+ broad_targeting_categories = self.connection(:broadtargetingcategories, options)
6
+ broad_targeting_categories.map! do |btc|
7
+ BroadTargetingCategory.new(btc[:id], btc.merge(
8
+ :access_token => options[:access_token] || self.access_token
9
+ ))
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module FbGraph
2
+ module Connections
3
+ module MutualFriends
4
+ def mutual_friends(friend, options = {})
5
+ friends = self.connection(:mutualfriends, options.merge(:connection_scope => connection_scope(friend)))
6
+ friends.map! do |friend|
7
+ User.new(friend[:id], friend.merge(:access_token => options[:access_token] || self.access_token))
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def connection_scope(friend)
14
+ if friend.is_a?(User)
15
+ friend.identifier
16
+ else
17
+ friend
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module FbGraph
2
+ module Connections
3
+ module UserAchievements
4
+ def achievements(options = {})
5
+ options[:access_token] ||= self.access_token
6
+ achievements = self.connection(:achievements, options)
7
+ achievements.map! do |achievement|
8
+ UserAchievement.new(achievement[:id], achievement.merge(:access_token => options[:access_token]))
9
+ end
10
+ end
11
+
12
+ def achieve!(achievement_url, options = {})
13
+ achievement = post options.merge(:connection => :achievements, :achievement => achievement_url)
14
+ UserAchievement.new achievement[:id], achievement
15
+ end
16
+
17
+ def unachieve!(achievement_url, options = {})
18
+ delete options.merge(:connection => :achievements, :achievement => achievement_url)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,40 @@
1
1
  module FbGraph
2
2
  class Exception < StandardError
3
3
  attr_accessor :code, :type, :message
4
+
5
+ ERROR_HEADER_MATCHERS = {
6
+ /invalid_token/ => "InvalidToken",
7
+ /invalid_request/ => "InvalidRequest"
8
+ }
9
+
10
+ def self.handle_httpclient_error(response, headers)
11
+ return nil unless response[:error]
12
+
13
+ # Check the WWW-Authenticate header, since it seems to be more standardized than the response
14
+ # body error information.
15
+ if www_authenticate = headers["WWW-Authenticate"]
16
+ # Session expiration/invalidation is very common. Check for that first.
17
+ if www_authenticate =~ /invalid_token/ && response[:error][:message] =~ /session has been invalidated/
18
+ raise InvalidSession.new("#{response[:error][:type]} :: #{response[:error][:message]}")
19
+ end
20
+
21
+ ERROR_HEADER_MATCHERS.keys.each do |matcher|
22
+ if matcher =~ www_authenticate
23
+ exception_class = FbGraph::const_get(ERROR_HEADER_MATCHERS[matcher])
24
+ raise exception_class.new("#{response[:error][:type]} :: #{response[:error][:message]}")
25
+ end
26
+ end
27
+ end
28
+
29
+ # If we can't match on WWW-Authenticate, use the type
30
+ case response[:error][:type]
31
+ when /OAuth/
32
+ raise Unauthorized.new("#{response[:error][:type]} :: #{response[:error][:message]}")
33
+ else
34
+ raise BadRequest.new("#{response[:error][:type]} :: #{response[:error][:message]}")
35
+ end
36
+ end
37
+
4
38
  def initialize(code, message, body = '')
5
39
  @code = code
6
40
  if body.blank?
@@ -30,4 +64,10 @@ module FbGraph
30
64
  super 404, message, body
31
65
  end
32
66
  end
33
- end
67
+
68
+ class InvalidToken < Unauthorized; end
69
+
70
+ class InvalidSession < InvalidToken; end
71
+
72
+ class InvalidRequest < BadRequest; end
73
+ end
@@ -120,21 +120,12 @@ module FbGraph
120
120
  _response_.map!(&:with_indifferent_access)
121
121
  when Hash
122
122
  _response_ = _response_.with_indifferent_access
123
- handle_httpclient_error(_response_) if _response_[:error]
123
+ Exception.handle_httpclient_error(_response_, response.headers) if _response_[:error]
124
124
  _response_
125
125
  end
126
126
  end
127
127
  rescue JSON::ParserError
128
128
  raise Exception.new(response.status, 'Unparsable Error Response')
129
129
  end
130
-
131
- def handle_httpclient_error(response)
132
- case response[:error][:type]
133
- when /OAuth/
134
- raise Unauthorized.new("#{response[:error][:type]} :: #{response[:error][:message]}")
135
- else
136
- raise BadRequest.new("#{response[:error][:type]} :: #{response[:error][:message]}")
137
- end
138
- end
139
130
  end
140
- end
131
+ end
@@ -22,6 +22,7 @@ module FbGraph
22
22
  include Connections::Links
23
23
  include Connections::Movies
24
24
  include Connections::Music
25
+ include Connections::MutualFriends
25
26
  include Connections::Notes
26
27
  include Connections::Outbox
27
28
  include Connections::Payments
@@ -33,6 +34,7 @@ module FbGraph
33
34
  include Connections::Tagged
34
35
  include Connections::Television
35
36
  include Connections::Threads
37
+ include Connections::UserAchievements
36
38
  include Connections::Videos
37
39
  extend Searchable
38
40
 
@@ -0,0 +1,35 @@
1
+ module FbGraph
2
+ class UserAchievement < Node
3
+ include Connections::Comments
4
+ include Connections::Likes
5
+
6
+ attr_accessor :from, :created_time, :application, :achievement
7
+
8
+ def initialize(identifier, attributes = {})
9
+ super
10
+
11
+ if from = attributes[:from]
12
+ @from = User.new(from[:id], from)
13
+ end
14
+ if created_time = attributes[:created_time] || attributes[:publish_time]
15
+ @created_time = Time.parse(created_time).utc
16
+ end
17
+ if application = attributes[:application]
18
+ application[:link] = application[:url] # for some reason, FB uses "url" only here..
19
+ @application = Application.new(application[:id], application)
20
+ end
21
+ if achievement = attributes[:achievement]
22
+ @achievement = Achievement.new(achievement[:id], achievement)
23
+ end
24
+
25
+ # cached connection
26
+ @_likes_ = Collection.new(attributes[:likes])
27
+ @_comments_ = Collection.new(attributes[:comments])
28
+ end
29
+
30
+ def destroy(options = {})
31
+ options[:access_token] ||= self.access_token
32
+ from.unachieve!(achievement.url, options)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe FbGraph::Achievement do
4
+ subject { achievement }
5
+ let(:achievement) { FbGraph::Achievement.new(attributes[:id], attributes) }
6
+ let(:attributes) do
7
+ {
8
+ :id => "10150310611431721",
9
+ :url => "http:\/\/fbgraphsample.heroku.com\/achievements\/1",
10
+ :type => "game.achievement",
11
+ :title => "1st Achievement",
12
+ :image => [{
13
+ :url => "http:\/\/matake.jp\/images\/nov.gif"
14
+ }],
15
+ :description => "For testing purpose",
16
+ :data => {
17
+ :points => 50
18
+ },
19
+ :updated_time => "2011-09-27T08:06:59+0000",
20
+ :application => {
21
+ :id => "134145643294322",
22
+ :name => "gem sample",
23
+ :url => "http:\/\/www.facebook.com\/apps\/application.php?id=134145643294322"
24
+ },
25
+ :context => {
26
+ :display_order => 0
27
+ }
28
+ }
29
+ end
30
+
31
+ [:type, :title, :url, :description, :data, :context].each do |key|
32
+ its(key) { should == attributes[key] }
33
+ end
34
+
35
+ its(:image) { should == attributes[:image].first[:url] }
36
+ its(:images) { should == attributes[:image].collect { |h| h[:url] } }
37
+ its(:points) { should == 50 }
38
+ its(:display_order) { should == 0 }
39
+ its(:updated_time) { should == Time.parse(attributes[:updated_time]).utc }
40
+ describe 'application' do
41
+ subject { achievement.application }
42
+ its(:name) { should == attributes[:application][:name] }
43
+ its(:link) { should == attributes[:application][:url] }
44
+ end
45
+
46
+ describe '#destroy' do
47
+ it 'should call DELETE /:app_id/achievements' do
48
+ expect { achievement.destroy }.should request_to('134145643294322/achievements', :delete)
49
+ end
50
+
51
+ it 'should delete achievement' do
52
+ mock_graph :delete, '134145643294322/achievements', 'true', :access_token => 'app_token', :params => {
53
+ :achievement => achievement.url
54
+ } do
55
+ achievement.destroy(:access_token => 'app_token').should be_true
56
+ end
57
+ end
58
+ end
59
+ end