reagent-fleakr 0.2.1 → 0.3.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/{README.markdown → README.rdoc} +53 -26
  2. data/Rakefile +3 -3
  3. data/lib/fleakr/api/request.rb +58 -0
  4. data/lib/fleakr/api/response.rb +35 -0
  5. data/lib/fleakr/objects/contact.rb +31 -0
  6. data/lib/fleakr/objects/error.rb +22 -0
  7. data/lib/fleakr/objects/group.rb +26 -0
  8. data/lib/fleakr/objects/image.rb +51 -0
  9. data/lib/fleakr/objects/photo.rb +55 -0
  10. data/lib/fleakr/objects/search.rb +33 -0
  11. data/lib/fleakr/objects/set.rb +45 -0
  12. data/lib/fleakr/objects/user.rb +106 -0
  13. data/lib/fleakr/support/attribute.rb +28 -0
  14. data/lib/fleakr/support/object.rb +88 -0
  15. data/lib/fleakr/version.rb +3 -3
  16. data/lib/fleakr.rb +66 -11
  17. data/test/fixtures/contacts.getPublicList.xml +7 -0
  18. data/test/fixtures/groups.pools.getPhotos.xml +7 -0
  19. data/test/fixtures/people.getInfo.xml +1 -1
  20. data/test/fixtures/photos.getSizes.xml +10 -0
  21. data/test/test_helper.rb +21 -6
  22. data/test/unit/fleakr/api/request_test.rb +93 -0
  23. data/test/{fleakr → unit/fleakr/api}/response_test.rb +2 -2
  24. data/test/unit/fleakr/objects/contact_test.rb +58 -0
  25. data/test/{fleakr → unit/fleakr/objects}/error_test.rb +2 -2
  26. data/test/{fleakr → unit/fleakr/objects}/group_test.rb +6 -2
  27. data/test/unit/fleakr/objects/image_test.rb +76 -0
  28. data/test/unit/fleakr/objects/photo_test.rb +101 -0
  29. data/test/{fleakr → unit/fleakr/objects}/search_test.rb +21 -16
  30. data/test/{fleakr → unit/fleakr/objects}/set_test.rb +17 -12
  31. data/test/{fleakr → unit/fleakr/objects}/user_test.rb +44 -3
  32. data/test/{fleakr → unit/fleakr/support}/attribute_test.rb +2 -2
  33. data/test/{fleakr → unit/fleakr/support}/object_test.rb +3 -3
  34. data/test/unit/fleakr_test.rb +44 -0
  35. metadata +43 -29
  36. data/lib/fleakr/attribute.rb +0 -26
  37. data/lib/fleakr/error.rb +0 -10
  38. data/lib/fleakr/group.rb +0 -12
  39. data/lib/fleakr/image.rb +0 -35
  40. data/lib/fleakr/object.rb +0 -75
  41. data/lib/fleakr/photo.rb +0 -26
  42. data/lib/fleakr/request.rb +0 -45
  43. data/lib/fleakr/response.rb +0 -21
  44. data/lib/fleakr/search.rb +0 -31
  45. data/lib/fleakr/set.rb +0 -22
  46. data/lib/fleakr/user.rb +0 -33
  47. data/test/fleakr/image_test.rb +0 -82
  48. data/test/fleakr/photo_test.rb +0 -64
  49. data/test/fleakr/request_test.rb +0 -99
@@ -0,0 +1,33 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+ class Search
4
+
5
+ # Create a new search
6
+ def initialize(search_options)
7
+ @search_options = search_options
8
+ end
9
+
10
+ # Retrieve search results from the API
11
+ def results
12
+ if @results.nil?
13
+ response = Fleakr::Api::Request.with_response!('photos.search', parameters)
14
+ @results = (response.body/'rsp/photos/photo').map do |flickr_photo|
15
+ Photo.new(flickr_photo)
16
+ end
17
+ end
18
+ @results
19
+ end
20
+
21
+ private
22
+ def tag_list
23
+ Array(@search_options[:tags]).join(',')
24
+ end
25
+
26
+ def parameters
27
+ @search_options.merge!(:tags => tag_list) if tag_list.length > 0
28
+ @search_options
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+
4
+ # = Set
5
+ #
6
+ # == Attributes
7
+ #
8
+ # [id] The ID for this photoset
9
+ # [title] The title of this photoset
10
+ # [description] The description of this set
11
+ #
12
+ # == Associations
13
+ #
14
+ # [photos] The collection of photos for this set. See Fleakr::Objects::Photo
15
+ #
16
+ class Set
17
+
18
+ include Fleakr::Support::Object
19
+
20
+ has_many :photos, :using => :photoset_id
21
+
22
+ flickr_attribute :id, :attribute => 'id'
23
+ flickr_attribute :title
24
+ flickr_attribute :description
25
+
26
+ find_all :by_user_id, :call => 'photosets.getList', :path => 'photosets/photoset'
27
+
28
+ # Save all photos in this set to the specified directory using the specified size. Allowed
29
+ # Sizes include <tt>:square</tt>, <tt>:small</tt>, <tt>:thumbnail</tt>, <tt>:medium</tt>,
30
+ # <tt>:large</tt>, and <tt>:original</tt>. When saving the set, this # method will create
31
+ # a subdirectory based on the set's title.
32
+ #
33
+ def save_to(path, size)
34
+ target = "#{path}/#{self.title}"
35
+ FileUtils.mkdir(target) unless File.exist?(target)
36
+
37
+ self.photos.each do |photo|
38
+ image = photo.send(size)
39
+ image.save_to(target) unless image.nil?
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,106 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+
4
+ # = User
5
+ #
6
+ # == Accessors
7
+ #
8
+ # This class maps directly onto the flickr.people.* API methods and provides the following attributes
9
+ # for a user:
10
+ #
11
+ # [id] The ID for this user (also referred to as the NSID in the API docs)
12
+ # [username] This user's username
13
+ # [name] This user's full name (if entered)
14
+ # [photos_url] The direct URL to this user's photostream
15
+ # [profile_url] The direct URL to this user's profile
16
+ # [photos_count] The number of photos that this user has uploaded
17
+ # [icon_url] This user's buddy icon (or a default one if an icon wasn't uploaded)
18
+ # [pro?] Does this user have a pro account?
19
+ # [admin?] Is this user an admin?
20
+ #
21
+ # == Associations
22
+ #
23
+ # The User class is pretty central to many of the other data available across the system, so there are a
24
+ # few associations available to a user:
25
+ #
26
+ # [sets] A list of this user's public sets (newest first). See Fleakr::Objects::Set for more information.
27
+ # [groups] A list of this user's public groups. See Fleakr::Objects::Group.
28
+ # [photos] A list of this user's public photos (newest first). See Fleakr::Objects::Photo.
29
+ # [contacts] A list of this user's contacts - these are simply User objects
30
+ #
31
+ # == Examples
32
+ #
33
+ # Access to a specific user is typically done through the Fleakr.user method:
34
+ #
35
+ # user = Fleakr.user('brownout')
36
+ # user.id
37
+ # user.username
38
+ # user.sets
39
+ # user.contacts
40
+ #
41
+ class User
42
+
43
+ include Fleakr::Support::Object
44
+
45
+ def self.lazily_load(*attributes)
46
+ options = attributes.extract_options!
47
+
48
+ attributes.each do |attribute|
49
+ class_eval <<-CODE
50
+ def #{attribute}_with_loading
51
+ self.send(:#{options[:with]}) if @#{attribute}.nil?
52
+ #{attribute}_without_loading
53
+ end
54
+ alias_method_chain :#{attribute}, :loading
55
+ CODE
56
+ end
57
+ end
58
+
59
+ flickr_attribute :id, :xpath => 'rsp/user', :attribute => 'nsid'
60
+ flickr_attribute :username, :xpath => 'rsp/user/username'
61
+ flickr_attribute :name, :xpath => 'rsp/person/realname'
62
+ flickr_attribute :photos_url, :xpath => 'rsp/person/photosurl'
63
+ flickr_attribute :profile_url, :xpath => 'rsp/person/profileurl'
64
+ flickr_attribute :photos_count, :xpath => 'rsp/person/photos/count'
65
+ flickr_attribute :icon_server, :xpath => 'rsp/person', :attribute => 'iconserver'
66
+ flickr_attribute :icon_farm, :xpath => 'rsp/person', :attribute => 'iconfarm'
67
+ flickr_attribute :pro, :xpath => 'rsp/person', :attribute => 'ispro'
68
+ flickr_attribute :admin, :xpath => 'rsp/person', :attribute => 'isadmin'
69
+
70
+ has_many :sets, :groups, :photos, :contacts
71
+
72
+ find_one :by_username, :call => 'people.findByUsername'
73
+ find_one :by_email, :using => :find_email, :call => 'people.findByEmail'
74
+
75
+ lazily_load :name, :photos_url, :profile_url, :photos_count, :with => :load_info
76
+ lazily_load :icon_server, :icon_farm, :pro, :admin, :with => :load_info
77
+
78
+ scoped_search
79
+
80
+ # Is this a pro account?
81
+ def pro?
82
+ (self.pro.to_i == 0) ? false : true
83
+ end
84
+
85
+ # Is this user an admin?
86
+ def admin?
87
+ (self.admin.to_i == 0) ? false : true
88
+ end
89
+
90
+ # This user's buddy icon
91
+ def icon_url
92
+ if self.icon_server.to_i > 0
93
+ "http://farm#{self.icon_farm}.static.flickr.com/#{self.icon_server}/buddyicons/#{self.id}.jpg"
94
+ else
95
+ 'http://www.flickr.com/images/buddyicon.jpg'
96
+ end
97
+ end
98
+
99
+ def load_info # :nodoc:
100
+ response = Fleakr::Api::Request.with_response!('people.getInfo', :user_id => self.id)
101
+ self.populate_from(response.body)
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,28 @@
1
+ module Fleakr
2
+ module Support # :nodoc:all
3
+ class Attribute
4
+
5
+ attr_reader :name, :xpath, :attribute
6
+
7
+ def initialize(name, options = {})
8
+ @name = name.to_sym
9
+ @attribute = options[:attribute]
10
+
11
+ @xpath = options[:xpath]
12
+ @xpath ||= @name.to_s unless @attribute
13
+ end
14
+
15
+ def value_from(document)
16
+ node = document
17
+
18
+ begin
19
+ node = document.at(self.xpath) if self.xpath
20
+ self.attribute.nil? ? node.inner_text : node[self.attribute]
21
+ rescue NoMethodError
22
+ nil
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,88 @@
1
+ module Fleakr
2
+ module Support # :nodoc:all
3
+ module Object
4
+
5
+ module ClassMethods
6
+
7
+ def attributes
8
+ @attributes ||= []
9
+ end
10
+
11
+ def flickr_attribute(name, options = {})
12
+ self.attributes << Attribute.new(name, options)
13
+ class_eval "attr_accessor :#{name}"
14
+ end
15
+
16
+ def has_many(*attributes)
17
+ options = attributes.extract_options!
18
+ class_name = self.name
19
+
20
+ attributes.each do |attribute|
21
+ target = "Fleakr::Objects::#{attribute.to_s.classify}"
22
+ finder_attribute = options[:using].nil? ? "#{class_name.demodulize.underscore}_id": options[:using]
23
+ class_eval <<-CODE
24
+ def #{attribute}
25
+ @#{attribute} ||= #{target}.send("find_all_by_#{finder_attribute}".to_sym, self.id)
26
+ end
27
+ CODE
28
+ end
29
+ end
30
+
31
+ def find_all(condition, options)
32
+ attribute = options[:using].nil? ? condition.to_s.sub(/^by_/, '') : options[:using]
33
+ target_class = options[:class_name].nil? ? self.name : "Fleakr::Objects::#{options[:class_name]}"
34
+
35
+ class_eval <<-CODE
36
+ def self.find_all_#{condition}(value)
37
+ response = Fleakr::Api::Request.with_response!('#{options[:call]}', :#{attribute} => value)
38
+ (response.body/'rsp/#{options[:path]}').map {|e| #{target_class}.new(e) }
39
+ end
40
+ CODE
41
+ end
42
+
43
+ def find_one(condition, options)
44
+ attribute = options[:using].nil? ? condition.to_s.sub(/^by_/, '') : options[:using]
45
+
46
+ class_eval <<-CODE
47
+ def self.find_#{condition}(value)
48
+ response = Fleakr::Api::Request.with_response!('#{options[:call]}', :#{attribute} => value)
49
+ #{self.name}.new(response.body)
50
+ end
51
+ CODE
52
+ end
53
+
54
+ def scoped_search
55
+ key = "#{self.name.demodulize.underscore.downcase}_id".to_sym
56
+
57
+ class_eval <<-CODE
58
+ def search(search_text)
59
+ Fleakr::Objects::Search.new(:text => search_text, :#{key} => self.id).results
60
+ end
61
+ CODE
62
+ end
63
+
64
+ end
65
+
66
+ module InstanceMethods
67
+
68
+ def initialize(document = nil)
69
+ self.populate_from(document) unless document.nil?
70
+ end
71
+
72
+ def populate_from(document)
73
+ self.class.attributes.each do |attribute|
74
+ value = attribute.value_from(document)
75
+ self.send("#{attribute.name}=".to_sym, value) unless value.nil?
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ def self.included(other)
82
+ other.send(:extend, Fleakr::Support::Object::ClassMethods)
83
+ other.send(:include, Fleakr::Support::Object::InstanceMethods)
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -1,9 +1,9 @@
1
1
  module Fleakr
2
- module Version
2
+ module Version # :nodoc:
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 2
6
- TINY = 1
5
+ MINOR = 3
6
+ TINY = 0
7
7
 
8
8
  def self.to_s
9
9
  [MAJOR, MINOR, TINY].join('.')
data/lib/fleakr.rb CHANGED
@@ -3,17 +3,72 @@ $:.unshift(File.dirname(__FILE__))
3
3
  require 'uri'
4
4
  require 'cgi'
5
5
  require 'net/http'
6
+ require 'rubygems'
6
7
  require 'hpricot'
7
8
  require 'activesupport'
8
9
 
9
- require 'fleakr/object'
10
- require 'fleakr/attribute'
11
- require 'fleakr/photo'
12
- require 'fleakr/request'
13
- require 'fleakr/response'
14
- require 'fleakr/error'
15
- require 'fleakr/set'
16
- require 'fleakr/user'
17
- require 'fleakr/group'
18
- require 'fleakr/search'
19
- require 'fleakr/image'
10
+ %w(support api objects).each do |path|
11
+ full_path = File.expand_path(File.dirname(__FILE__)) + "/fleakr/#{path}"
12
+ Dir["#{full_path}/*.rb"].each {|f| require f }
13
+ end
14
+
15
+ # = Fleakr: A teeny tiny gem to interface with Flickr
16
+ #
17
+ # Getting started is easy, just make sure you have a valid API key from Flickr and you can
18
+ # then start making any non-authenticated request to pull back data for yours and others'
19
+ # photostreams, sets, contacts, groups, etc...
20
+ #
21
+ # For now, all activity originates from a single user which you can find by username or
22
+ # email address.
23
+ #
24
+ # Example:
25
+ #
26
+ # require 'rubygems'
27
+ # require 'fleakr'
28
+ #
29
+ # # Our API key is ABC123 (http://www.flickr.com/services/api/keys/apply/)
30
+ # Fleakr.api_key = 'ABC123'
31
+ # user = Fleakr.user('bees')
32
+ # user = Fleakr.user('user@host.com')
33
+ # # Grab a list of sets
34
+ # user.sets
35
+ # # Grab a list of the user's public groups
36
+ # user.groups
37
+ #
38
+ # To see what other associations and attributes are available, see the Fleakr::Objects::User class
39
+ #
40
+ module Fleakr
41
+
42
+ mattr_accessor :api_key
43
+
44
+ # Find a user based on some unique user data. This method will try to find
45
+ # the user based on username and will fall back to email if that fails. Example:
46
+ #
47
+ # Fleakr.api_key = 'ABC123'
48
+ # Fleakr.user('the decapitator') # => #<Fleakr::Objects::User:0x692648 @username="the decapitator", @id="21775151@N06">
49
+ # Fleakr.user('user@host.com') # => #<Fleakr::Objects::User:0x11f484c @username="bckspcr", @id="84481630@N00">
50
+ #
51
+ def self.user(user_data)
52
+ begin
53
+ Objects::User.find_by_username(user_data)
54
+ rescue Api::Request::ApiError
55
+ Objects::User.find_by_email(user_data)
56
+ end
57
+ end
58
+
59
+ # Search all photos on the Flickr site. By default, this searches based on text, but you can pass
60
+ # different search parameters (passed as hash keys):
61
+ #
62
+ # [tags] The list of tags to search on (either as an array or comma-separated)
63
+ # [user_id] Scope the search to this user
64
+ # [group_id] Scope the search to this group
65
+ #
66
+ # If you're interested in User- and Group-scoped searches, you may want to use User#search and Group#search
67
+ # instead.
68
+ #
69
+ def self.search(params)
70
+ params = {:text => params} unless params.is_a?(Hash)
71
+ Objects::Search.new(params).results
72
+ end
73
+
74
+ end
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <rsp stat="ok">
3
+ <contacts page="1" pages="1" per_page="1000" perpage="1000" total="17">
4
+ <contact nsid="9302864@N42" username="blinky" iconserver="2263" iconfarm="3" ignored="0" />
5
+ <contact nsid="63204625@N20" username="inky" iconserver="53" iconfarm="1" ignored="0" />
6
+ </contacts>
7
+ </rsp>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <rsp stat="ok">
3
+ <photos page="1" pages="796" perpage="100" total="79509">
4
+ <photo id="3101830908" owner="64008250@N00" secret="e85e249e72" server="3042" farm="4" title="Canal Gate" ispublic="1" isfriend="0" isfamily="0" ownername="oakraidr" dateadded="1229049777" />
5
+ <photo id="3100780049" owner="98821864@N00" secret="f46fbe211b" server="3274" farm="4" title="2alarm" ispublic="1" isfriend="0" isfamily="0" ownername="LooknFeel" dateadded="1229042651" />
6
+ </photos>
7
+ </rsp>
@@ -2,7 +2,7 @@
2
2
  <rsp stat="ok">
3
3
  <person id="31066442@N69" nsid="31066442@N69" isadmin="0" ispro="1" iconserver="30" iconfarm="1">
4
4
  <username>frootpantz</username>
5
- <realname />
5
+ <realname>Sir Froot Pantz</realname>
6
6
  <mbox_sha1sum>e52ed1e5b91c763694995460e9796fc2adc02019</mbox_sha1sum>
7
7
  <location />
8
8
  <photosurl>http://www.flickr.com/photos/frootpantz/</photosurl>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <rsp stat="ok">
3
+ <sizes canblog="0" canprint="0" candownload="1">
4
+ <size label="Square" width="75" height="75" source="http://farm4.static.flickr.com/3093/2409912100_71e14ed08a_s.jpg" url="http://www.flickr.com/photos/the_decapitator/2409912100/sizes/sq/" media="photo" />
5
+ <size label="Thumbnail" width="100" height="67" source="http://farm4.static.flickr.com/3093/2409912100_71e14ed08a_t.jpg" url="http://www.flickr.com/photos/the_decapitator/2409912100/sizes/t/" media="photo" />
6
+ <size label="Small" width="240" height="160" source="http://farm4.static.flickr.com/3093/2409912100_71e14ed08a_m.jpg" url="http://www.flickr.com/photos/the_decapitator/2409912100/sizes/s/" media="photo" />
7
+ <size label="Medium" width="500" height="334" source="http://farm4.static.flickr.com/3093/2409912100_71e14ed08a.jpg" url="http://www.flickr.com/photos/the_decapitator/2409912100/sizes/m/" media="photo" />
8
+ <size label="Original" width="700" height="467" source="http://farm4.static.flickr.com/3093/2409912100_3305c4a108_o.jpg" url="http://www.flickr.com/photos/the_decapitator/2409912100/sizes/o/" media="photo" />
9
+ </sizes>
10
+ </rsp>
data/test/test_helper.rb CHANGED
@@ -15,16 +15,31 @@ class Test::Unit::TestCase
15
15
  end
16
16
  end
17
17
 
18
+ def self.should_search_by(key)
19
+ it "should be able to perform a scoped search by :#{key}" do
20
+ photos = [stub()]
21
+ search = stub(:results => photos)
22
+
23
+ klass = self.class.name.sub(/Test$/, '').constantize
24
+
25
+ instance = klass.new
26
+ instance.stubs(:id).with().returns('1')
27
+
28
+ Fleakr::Objects::Search.expects(:new).with(:text => 'foo', key => '1').returns(search)
29
+
30
+ instance.search('foo').should == photos
31
+ end
32
+ end
33
+
18
34
  def self.should_have_many(*attributes)
19
35
  class_name = self.name.demodulize.sub(/Test$/, '')
20
- this_klass = "Fleakr::#{class_name}".constantize
36
+ this_klass = "Fleakr::Objects::#{class_name}".constantize
21
37
 
22
38
  options = attributes.extract_options!
23
39
  finder_attribute = options[:using].nil? ? "#{class_name.downcase}_id" : options[:using]
24
40
 
25
41
  attributes.each do |attribute|
26
- target_klass = "Fleakr::#{attribute.to_s.singularize.classify}".constantize
27
-
42
+ target_klass = "Fleakr::Objects::#{attribute.to_s.singularize.classify}".constantize
28
43
  it "should be able to retrieve the #{class_name.downcase}'s #{attribute}" do
29
44
  results = [stub()]
30
45
  object = this_klass.new
@@ -46,7 +61,7 @@ class Test::Unit::TestCase
46
61
 
47
62
  def self.should_find_one(thing, options)
48
63
  class_name = thing.to_s.singularize.camelcase
49
- klass = "Fleakr::#{class_name}".constantize
64
+ klass = "Fleakr::Objects::#{class_name}".constantize
50
65
  object_type = class_name.downcase
51
66
 
52
67
  options[:with] = options[:by] if options[:with].nil?
@@ -63,7 +78,7 @@ class Test::Unit::TestCase
63
78
 
64
79
  def self.should_find_all(thing, options)
65
80
  class_name = thing.to_s.singularize.camelcase
66
- klass = "Fleakr::#{class_name}".constantize
81
+ klass = "Fleakr::Objects::#{class_name}".constantize
67
82
  object_type = class_name.downcase
68
83
 
69
84
  it "should be able to find all #{thing} by #{options[:by]}" do
@@ -93,7 +108,7 @@ class Test::Unit::TestCase
93
108
 
94
109
  def mock_request_cycle(options)
95
110
  response = stub(:body => Hpricot.XML(read_fixture(options[:for])))
96
- Fleakr::Request.expects(:with_response!).with(options[:for], options[:with]).returns(response)
111
+ Fleakr::Api::Request.expects(:with_response!).with(options[:for], options[:with]).returns(response)
97
112
 
98
113
  response
99
114
  end