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,32 @@
1
+ module Fogli
2
+ module Util
3
+ module Options
4
+ # Verifies that an options hash has all the required keys and
5
+ # also merges it with a defaults hash if given. This is an
6
+ # internal helper method.
7
+ #
8
+ # @param [Hash] options
9
+ # @param [Array] required Array of required keys.
10
+ # @param [Hash] defaults
11
+ # @return [Hash]
12
+ def verify_options(data, options=nil)
13
+ data ||= {}
14
+ options ||= {}
15
+ options[:valid_keys] ||= options[:default].keys if options[:default]
16
+
17
+ # Merge in the default data and remove any invalid keys if
18
+ # valid keys are specified.
19
+ ops = options[:default] || {}
20
+ ops.merge!(data)
21
+ ops.reject! { |k,v| !options[:valid_keys].include?(k) } if options[:valid_keys]
22
+
23
+ if options[:required_keys]
24
+ required = ops.reject { |k,v| !options[:required_keys].include?(k) || v.nil? }
25
+ raise ArgumentError.new("Missing required options: #{options[:required_keys].inspect}") if required.length != options[:required_keys].length
26
+ end
27
+
28
+ ops
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module Fogli
2
+ class Video < FacebookObject
3
+ property :from, :message, :description, :length, :created_time
4
+
5
+ connection :comments, :class => :Comment
6
+ end
7
+ end
@@ -0,0 +1,124 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class FacebookGraphTest < Test::Unit::TestCase
4
+ setup do
5
+ @klass = Fogli::FacebookGraph
6
+ end
7
+
8
+ context "requesting" do
9
+ teardown do
10
+ Fogli.access_token = nil
11
+ end
12
+
13
+ [:get, :post, :delete, :head].each do |method|
14
+ should "properly request #{method} type" do
15
+ @klass.expects(:request).with(method, "/foo", {}).once
16
+ @klass.send(method, "/foo", {})
17
+ end
18
+
19
+ should "properly have a raw request for #{method} type" do
20
+ RestClient.expects(method).with("/foo", {}).once
21
+ @klass.send("raw_#{method}".to_sym, "/foo", {})
22
+ end
23
+ end
24
+
25
+ should "use http by default" do
26
+ RestClient.expects(:get).with("http://#{@klass::GRAPH_DOMAIN}/foo", {}).once
27
+ @klass.request(:get, "/foo")
28
+ end
29
+
30
+ should "use https if an access token is set" do
31
+ Fogli.access_token = "foo"
32
+ RestClient.expects(:get).with("https://#{@klass::GRAPH_DOMAIN}/foo?access_token=foo", {}).once
33
+ @klass.request(:get, "/foo")
34
+ end
35
+
36
+ should "append parameters for GET/DELETE" do
37
+ params = { "foo" => "bar baz" }
38
+ RestClient.expects(:get).with("http://#{@klass::GRAPH_DOMAIN}/foo?foo=bar+baz", {}).once
39
+ @klass.request(:get, "/foo", params)
40
+ end
41
+
42
+ should "convert params to strings for GET/DELETE" do
43
+ params = { :limit => 1 }
44
+ RestClient.expects(:get).with("http://#{@klass::GRAPH_DOMAIN}/foo?limit=1", {}).once
45
+ @klass.request(:get, "/foo", params)
46
+ end
47
+
48
+ should "pass params into function for POST" do
49
+ params = { :foo => :baz }
50
+ RestClient.expects(:post).with("http://#{@klass::GRAPH_DOMAIN}/foo", params).once
51
+ @klass.request(:post, "/foo", params)
52
+ end
53
+ end
54
+
55
+ context "error checking" do
56
+ def response(data)
57
+ response = mock("response")
58
+ response.stubs(:headers).returns({:content_type => "text/plain"})
59
+ response.stubs(:body).returns(data)
60
+ response.stubs(:code).returns(200)
61
+ response
62
+ end
63
+
64
+ should "return the data if the data is fine" do
65
+ data = { "foo" => "bar" }
66
+
67
+ assert_equal data, @klass.error_check { response(data) }
68
+ end
69
+
70
+ should "raise an exception if the data represents an error" do
71
+ data = { "error" => { "type" => "foo", "message" => "baz" }}
72
+ assert_raises(Fogli::Exception) do
73
+ @klass.error_check { response(data) }
74
+ end
75
+ end
76
+
77
+ should "raise an exception for a 500 response code" do
78
+ resp = response({})
79
+ resp.stubs(:code).returns(500)
80
+ assert_raises(Fogli::Exception) do
81
+ @klass.error_check { resp }
82
+ end
83
+ end
84
+
85
+ should "catch exceptions with responses" do
86
+ data = { "foo" => "bar" }
87
+ result = @klass.error_check do
88
+ e = RestClient::Exception.new
89
+ e.response = response(data)
90
+ raise e
91
+ end
92
+ end
93
+ end
94
+
95
+ context "parsing response" do
96
+ setup do
97
+ @headers = {}
98
+ @body = "[1]"
99
+
100
+ @response = mock("response")
101
+ @response.stubs(:headers).returns(@headers)
102
+ @response.stubs(:body).returns(@body)
103
+ end
104
+
105
+ should "simply return if the content-type is text/plain" do
106
+ @headers[:content_type] = "text/plain; foo=bar"
107
+ assert_equal @body, @klass.parse_response(@response)
108
+ end
109
+
110
+ should "parse JSON otherwise" do
111
+ @headers[:content_type] = "anything"
112
+ result= @klass.parse_response(@response)
113
+ assert result.is_a?(Array)
114
+ assert_equal 1, result[0]
115
+ end
116
+
117
+ should "return the raw body if a JSON parse error occurs" do
118
+ @headers[:content_type] = "whatever"
119
+ @body = "true"
120
+ @response.stubs(:body).returns(@body)
121
+ assert_equal @body, @klass.parse_response(@response)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class FacebookObjectConnectionProxyTest < Test::Unit::TestCase
4
+ setup do
5
+ @klass = Fogli::FacebookObject::ConnectionProxy
6
+
7
+ @parent = Fogli::FacebookObject.new
8
+ @connection_name = "friends"
9
+ @connection_options = { :class => :FacebookObject }
10
+ @instance = @klass.new(@parent, @connection_name, @connection_options)
11
+ end
12
+
13
+ context "loading" do
14
+ setup do
15
+ # Make sure no actual HTTP requests go through
16
+ @parent.stubs(:get)
17
+ @scope = Fogli::FacebookObject::ConnectionScope.new(@instance)
18
+ end
19
+
20
+ should "get the connection with the scope options and parse the data" do
21
+ @parent.expects(:get).with("/#{@connection_name}", @scope.options).once.returns(nil)
22
+ assert_equal({"data"=>[]}, @instance.load(@scope))
23
+ end
24
+ end
25
+
26
+ context "parsing data" do
27
+ should "instantiate for each data item" do
28
+ data = {"data" => [{"id"=>"1"},{"id"=>"2"}]}
29
+ @instance.stubs(:connection_class).returns(Fogli::FacebookObject)
30
+ result = @instance.parse_data(data)
31
+ assert_equal data["data"].length, result["data"].length
32
+ assert_equal "1", result["data"][0].id
33
+ end
34
+ end
35
+
36
+ context "connection class" do
37
+ should "just return the class if specified" do
38
+ @connection_options[:class] = :User
39
+ assert_equal Fogli::User, @instance.connection_class
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,91 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class FacebookObjectConnectionScopeTest < Test::Unit::TestCase
4
+ setup do
5
+ @klass = Fogli::FacebookObject::ConnectionScope
6
+
7
+ @proxy = Fogli::FacebookObject::ConnectionProxy.new(Fogli::FacebookObject.new,
8
+ :foo,
9
+ {:class => :Post})
10
+ @options = {}
11
+ @instance = @klass.new(@proxy, @options)
12
+ end
13
+
14
+ context "each" do
15
+ setup do
16
+ @instance.clear_cache
17
+ end
18
+
19
+ def stub_data(*values)
20
+ { "data" => values }
21
+ end
22
+
23
+ should "load the initial data if its not yet loaded" do
24
+ @instance.expects(:load!).once
25
+ @instance.each {}
26
+ end
27
+
28
+ should "load as page end is reached" do
29
+ @instance.expects(:load!).once.returns(false)
30
+
31
+ data = [stub_data(1,2,3), stub_data(4,5,6)]
32
+ @instance.instance_variable_set(:@_data, data)
33
+ result = @instance.inject(0) do |acc, i|
34
+ acc + i
35
+ end
36
+
37
+ assert_equal 21, result
38
+ end
39
+
40
+ should "limit the data to the specified amount" do
41
+ data = [stub_data(1,2,3), stub_data(4,5,6), stub_data(7)]
42
+ @instance.options[:limit] = 2
43
+ @instance.instance_variable_set(:@_data, data)
44
+ result = @instance.inject(0) { |a,i| a + 1 }
45
+ assert_equal 2, result
46
+ end
47
+ end
48
+
49
+ context "loading" do
50
+ setup do
51
+ @instance.clear_cache
52
+ end
53
+
54
+ should "default the fields to all of the parent's if not specified" do
55
+ assert @instance.options[:fields].nil? # sanity check
56
+ @proxy.expects(:load).with() do |scope|
57
+ assert scope.options[:fields]
58
+ assert_equal Fogli::Post.properties.keys.join(","), scope.options[:fields]
59
+ true
60
+ end
61
+
62
+ @instance.load!
63
+ end
64
+
65
+ should "load the first page if data hasn't been loaded yet" do
66
+ data = mock("data")
67
+ @proxy.expects(:load).with(@instance).returns(data)
68
+ assert @instance.load!
69
+ assert_equal data, @instance._data.first
70
+ end
71
+
72
+ should "load the next page if the data has already been loaded and has next page" do
73
+ data = { "paging" => { "next" => "foo" } }
74
+ raw = { "data" => [] }
75
+ @proxy.stubs(:load).returns(data)
76
+ @instance.load!
77
+ Fogli::FacebookGraph.expects(:raw_get).with(data["paging"]["next"]).once.returns(raw)
78
+ @proxy.expects(:parse_data).with(raw).returns(raw)
79
+ assert @instance.load!
80
+ assert_equal raw, @instance._data.last
81
+ end
82
+
83
+ should "return false if no next page is available" do
84
+ data = { "paging" => { } }
85
+ @proxy.stubs(:load).returns(data)
86
+ @instance.load!
87
+ Fogli::FacebookGraph.expects(:raw_get).never
88
+ assert !@instance.load!
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,50 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class FacebookObjectConnectionsTest < Test::Unit::TestCase
4
+ class FOCTestClass
5
+ include Fogli::FacebookObject::Connections
6
+ end
7
+
8
+ setup do
9
+ @klass = FOCTestClass
10
+ end
11
+
12
+ context "class methods" do
13
+ context "defining connections" do
14
+ teardown do
15
+ @klass.connections.clear
16
+ end
17
+
18
+ should "be able to define a connection" do
19
+ @klass.connection(:foo, :class => :bar)
20
+ assert @klass.connections.keys.include?(:foo)
21
+ end
22
+
23
+ should "be able to define multiple connections" do
24
+ @klass.connection(:foo, :bar, :class => :foo)
25
+ assert @klass.connections.keys.include?(:foo)
26
+ assert @klass.connections.keys.include?(:bar)
27
+ end
28
+
29
+ should "raise an exception if a class is not specified" do
30
+ assert_raises(ArgumentError) {
31
+ @klass.connection(:foo)
32
+ }
33
+ end
34
+
35
+ should "be able to define options" do
36
+ @klass.connection(:foo, :class => :baz)
37
+ assert @klass.connections.keys.include?(:foo)
38
+ assert_equal :baz, @klass.connections[:foo][:class]
39
+ end
40
+
41
+ should "propagate connections" do
42
+ @subklass = Class.new(@klass)
43
+ @klass.connection(:foo, :class => :bar)
44
+ assert !@subklass.connections.keys.include?(:foo)
45
+ @klass.propagate_connections(@subklass)
46
+ assert @subklass.connections.keys.include?(:foo)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,79 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class FacebookObjectPropertiesTest < Test::Unit::TestCase
4
+ class FOPTestClass
5
+ include Fogli::FacebookObject::Properties
6
+ end
7
+
8
+ setup do
9
+ @klass = FOPTestClass
10
+ end
11
+
12
+ context "class methods" do
13
+ context "defining properties" do
14
+ setup do
15
+ @property_klass = @klass
16
+ end
17
+
18
+ teardown do
19
+ @property_klass.properties.clear
20
+ end
21
+
22
+ should "be able to define a property" do
23
+ @property_klass.property(:foo)
24
+ assert @property_klass.properties.keys.include?(:foo)
25
+ end
26
+
27
+ should "be able to define multiple properties" do
28
+ @property_klass.property(:foo, :bar)
29
+ assert @property_klass.properties.keys.include?(:foo)
30
+ assert @property_klass.properties.keys.include?(:bar)
31
+ end
32
+
33
+ should "be able to define options" do
34
+ @property_klass.property(:foo, :bar => :baz)
35
+ assert @property_klass.properties.keys.include?(:foo)
36
+ assert_equal :baz, @property_klass.properties[:foo][:bar]
37
+ end
38
+
39
+ should "propagate properties" do
40
+ @subklass = Class.new(@property_klass)
41
+ @property_klass.property :foo
42
+ assert !@subklass.properties.keys.include?(:foo)
43
+ @property_klass.propagate_properties(@subklass)
44
+ assert @subklass.properties.keys.include?(:foo)
45
+ end
46
+ end
47
+ end
48
+
49
+ context "with an instance" do
50
+ setup do
51
+ @klass.property :id
52
+ @instance = @klass.new
53
+ end
54
+
55
+ teardown do
56
+ @klass.properties.clear
57
+ end
58
+
59
+ should "set the properties based on the hash data" do
60
+ @instance.populate_properties({"id" => "foo"})
61
+ assert_equal "foo", @instance.id
62
+ end
63
+
64
+ should "set the properties based on the hash data [symbol]" do
65
+ @instance.populate_properties({:id => "foo"})
66
+ assert_equal "foo", @instance.id
67
+ end
68
+
69
+ should "detect and store NamedObjects" do
70
+ @instance.populate_properties({"id" => {"name" => "foo"}})
71
+ assert @instance.property_values[:id].is_a?(Fogli::NamedObject)
72
+ end
73
+
74
+ should "read NamedObject's names when reading a property" do
75
+ @instance.populate_properties({"id" => {"name" => "foo"}})
76
+ assert_equal "foo", @instance.id
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,69 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class ScopeMethodsTest < Test::Unit::TestCase
4
+ setup do
5
+ @module = Fogli::FacebookObject::ScopeMethods
6
+ end
7
+
8
+ context "scope methods" do
9
+ setup do
10
+ @klass = Class.new
11
+ @klass.send(:include, @module)
12
+ @instance = @klass.new
13
+ end
14
+
15
+ [:limit, :offset, :until, :since].each do |scope|
16
+ should "return a new connection scope for #{scope}" do
17
+ value = "#{scope}_foo"
18
+ result = @instance.send(scope, value)
19
+ assert_equal value, result.options[scope]
20
+ assert_equal @instance, result.proxy
21
+ end
22
+ end
23
+
24
+ should "initialize a scope for fields" do
25
+ tests = [
26
+ ["name,birthday,location"],
27
+ [:name, :birthday, :location],
28
+ [["name", :birthday], :location],
29
+ [["name"], [[[:birthday]], :location]]
30
+ ]
31
+
32
+ tests.each do |args|
33
+ scope = @instance.fields(*args)
34
+ assert_equal "name,birthday,location", scope.options[:fields]
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ context "scope initializer args" do
41
+ context "not a connection scope" do
42
+ setup do
43
+ @klass = Class.new
44
+ @klass.send(:include, @module)
45
+ @instance = @klass.new
46
+ end
47
+
48
+ should "return the proxy if not a connection scope" do
49
+ proxy, options = @instance._scope_initializer_args(:foo, :bar)
50
+ assert_equal @instance, proxy
51
+ assert_equal({:foo => :bar}, options)
52
+ end
53
+ end
54
+
55
+ context "in a connection scope" do
56
+ setup do
57
+ @proxy = mock("proxy")
58
+ @options = { :foo => :bar }
59
+ @instance = Fogli::FacebookObject::ConnectionScope.new(@proxy, @options)
60
+ end
61
+
62
+ should "return the proxy and merged options" do
63
+ proxy, options = @instance._scope_initializer_args(:bar, :baz)
64
+ assert_equal @proxy, proxy
65
+ assert_equal(@options.merge(:bar => :baz), options)
66
+ end
67
+ end
68
+ end
69
+ end