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,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