reagent-fleakr 0.2.1 → 0.3.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/{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