fleakr 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/README.rdoc +88 -57
  2. data/Rakefile +28 -9
  3. data/lib/fleakr.rb +25 -31
  4. data/lib/fleakr/api/option.rb +34 -34
  5. data/lib/fleakr/api/parameter_list.rb +23 -23
  6. data/lib/fleakr/objects.rb +4 -1
  7. data/lib/fleakr/objects/metadata.rb +35 -0
  8. data/lib/fleakr/objects/metadata_collection.rb +36 -0
  9. data/lib/fleakr/objects/photo.rb +15 -11
  10. data/lib/fleakr/objects/set.rb +17 -13
  11. data/lib/fleakr/objects/url.rb +83 -0
  12. data/lib/fleakr/objects/user.rb +8 -0
  13. data/lib/fleakr/support.rb +3 -1
  14. data/lib/fleakr/support/attribute.rb +2 -2
  15. data/lib/fleakr/support/object.rb +44 -29
  16. data/lib/fleakr/support/url_expander.rb +37 -0
  17. data/lib/fleakr/support/utility.rb +66 -0
  18. data/lib/fleakr/version.rb +5 -5
  19. data/test/fixtures/photos.getExif.xml +362 -0
  20. data/test/test_helper.rb +44 -47
  21. data/test/unit/fleakr/api/authentication_request_test.rb +12 -12
  22. data/test/unit/fleakr/api/file_parameter_test.rb +15 -15
  23. data/test/unit/fleakr/api/method_request_test.rb +1 -1
  24. data/test/unit/fleakr/api/option_test.rb +44 -44
  25. data/test/unit/fleakr/api/parameter_list_test.rb +40 -40
  26. data/test/unit/fleakr/api/response_test.rb +10 -10
  27. data/test/unit/fleakr/api/upload_request_test.rb +28 -28
  28. data/test/unit/fleakr/api/value_parameter_test.rb +10 -10
  29. data/test/unit/fleakr/core_ext/false_class_test.rb +5 -5
  30. data/test/unit/fleakr/core_ext/hash_test.rb +9 -9
  31. data/test/unit/fleakr/core_ext/true_class_test.rb +5 -5
  32. data/test/unit/fleakr/objects/authentication_token_test.rb +23 -23
  33. data/test/unit/fleakr/objects/collection_test.rb +23 -23
  34. data/test/unit/fleakr/objects/comment_test.rb +15 -15
  35. data/test/unit/fleakr/objects/contact_test.rb +11 -11
  36. data/test/unit/fleakr/objects/error_test.rb +8 -8
  37. data/test/unit/fleakr/objects/group_test.rb +13 -13
  38. data/test/unit/fleakr/objects/image_test.rb +4 -4
  39. data/test/unit/fleakr/objects/metadata_collection_test.rb +55 -0
  40. data/test/unit/fleakr/objects/metadata_test.rb +27 -0
  41. data/test/unit/fleakr/objects/photo_context_test.rb +23 -23
  42. data/test/unit/fleakr/objects/photo_test.rb +71 -64
  43. data/test/unit/fleakr/objects/search_test.rb +22 -22
  44. data/test/unit/fleakr/objects/set_test.rb +62 -40
  45. data/test/unit/fleakr/objects/tag_test.rb +32 -31
  46. data/test/unit/fleakr/objects/url_test.rb +185 -0
  47. data/test/unit/fleakr/objects/user_test.rb +43 -16
  48. data/test/unit/fleakr/support/attribute_test.rb +15 -15
  49. data/test/unit/fleakr/support/object_test.rb +77 -33
  50. data/test/unit/fleakr/support/request_test.rb +12 -12
  51. data/test/unit/fleakr/support/url_expander_test.rb +44 -0
  52. data/test/unit/fleakr/support/utility_test.rb +70 -0
  53. data/test/unit/fleakr_test.rb +48 -41
  54. metadata +43 -23
@@ -1,17 +1,17 @@
1
1
  module Fleakr
2
2
  module Objects # :nodoc:
3
-
3
+
4
4
  # = Set
5
5
  #
6
6
  # == Attributes
7
- #
7
+ #
8
8
  # [id] The ID for this photoset
9
9
  # [title] The title of this photoset
10
10
  # [description] The description of this set
11
11
  # [count] Count of photos in this set
12
- #
12
+ #
13
13
  # == Associations
14
- #
14
+ #
15
15
  # [photos] The collection of photos for this set. See Fleakr::Objects::Photo
16
16
  # [comments] All comments associated with this set. See Fleakr::Objects::Comment
17
17
  #
@@ -27,18 +27,18 @@ module Fleakr
27
27
  flickr_attribute :user_id, :from => '@owner'
28
28
 
29
29
  find_all :by_user_id, :call => 'photosets.getList', :path => 'photosets/photoset'
30
-
30
+
31
31
  find_one :by_id, :using => :photoset_id, :call => 'photosets.getInfo', :path => 'photoset'
32
32
 
33
33
  lazily_load :user_id, :with => :load_info
34
34
 
35
35
  # Save all photos in this set to the specified directory for the specified size. Allowed
36
- # Sizes include <tt>:square</tt>, <tt>:small</tt>, <tt>:thumbnail</tt>, <tt>:medium</tt>,
37
- # <tt>:large</tt>, and <tt>:original</tt>. When saving the set, this method will create
36
+ # Sizes include <tt>:square</tt>, <tt>:small</tt>, <tt>:thumbnail</tt>, <tt>:medium</tt>,
37
+ # <tt>:large</tt>, and <tt>:original</tt>. When saving the set, this method will create
38
38
  # a subdirectory based on the set's title.
39
39
  #
40
40
  def save_to(path, size)
41
- target = "#{path}/#{self.title}"
41
+ target = "#{path}/#{folder_name}"
42
42
  FileUtils.mkdir(target) unless File.exist?(target)
43
43
 
44
44
  self.photos.each_with_index do |photo, index|
@@ -46,34 +46,38 @@ module Fleakr
46
46
  image.save_to(target, file_prefix(index)) unless image.nil?
47
47
  end
48
48
  end
49
-
49
+
50
50
  def file_prefix(index) # :nodoc:
51
51
  sprintf("%0#{self.count.length}d_", (index + 1))
52
52
  end
53
53
 
54
+ def folder_name # :nodoc:
55
+ title.gsub("/", ' ').squeeze(' ')
56
+ end
57
+
54
58
  # Primary photo for this set. See Fleakr::Objects::Photo for more details.
55
59
  #
56
60
  def primary_photo
57
61
  @primary_photo ||= Photo.find_by_id(primary_photo_id)
58
62
  end
59
-
63
+
60
64
  # The URL for this set.
61
65
  #
62
66
  def url
63
67
  "http://www.flickr.com/photos/#{user_id}/sets/#{id}/"
64
68
  end
65
-
69
+
66
70
  # The user who created this set.
67
71
  #
68
72
  def user
69
73
  User.find_by_id(user_id)
70
74
  end
71
-
75
+
72
76
  def load_info # :nodoc:
73
77
  response = Fleakr::Api::MethodRequest.with_response!('photosets.getInfo', :photoset_id => self.id)
74
78
  self.populate_from(response.body)
75
79
  end
76
-
80
+
77
81
  end
78
82
  end
79
83
  end
@@ -0,0 +1,83 @@
1
+ module Fleakr
2
+ module Objects
3
+
4
+ class Url
5
+
6
+ def initialize(url)
7
+ @url = url
8
+ end
9
+
10
+ def path
11
+ expanded_path || original_path
12
+ end
13
+
14
+ def user_identifier
15
+ (resource_type == Set) ? parts[1] : parts[2]
16
+ end
17
+
18
+ def shortened?
19
+ !original_path.match(%r{^/p/[123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ]+$}).nil?
20
+ end
21
+
22
+ def resource_identifier
23
+ parts[3]
24
+ end
25
+
26
+ def user
27
+ @user ||= User.find_by_identifier(user_identifier)
28
+ end
29
+
30
+ def resource_type
31
+ if parts[1] == 'people'
32
+ User
33
+ elsif parts[1] == 'photos'
34
+ Photo
35
+ elsif parts[2] == 'sets'
36
+ Set
37
+ end
38
+ end
39
+
40
+ def collection?
41
+ resource_identifier.nil?
42
+ end
43
+
44
+ def resource
45
+ if resource_type == User
46
+ user
47
+ else
48
+ collection? ? resource_type.find_all_by_user_id(user.id) : resource_type.find_by_id(resource_identifier)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def expanded_path
55
+ Fleakr::Support::UrlExpander.expand(@url) if shortened?
56
+ end
57
+
58
+ def original_path
59
+ URI.parse(@url).path
60
+ end
61
+
62
+ def parts
63
+ path.match(matching_pattern)
64
+ end
65
+
66
+ def matching_pattern
67
+ @matching_pattern ||= patterns.detect {|p| path.match(p) }
68
+ end
69
+
70
+ def patterns
71
+ [
72
+ %r{^/photos/([^/]+)/(sets)/(\d+)},
73
+ %r{^/photos/([^/]+)/(sets)},
74
+ %r{^/(photos)/([^/]+)/(\d+)},
75
+ %r{^/(people)/([^/]+)},
76
+ %r{^/(photos)/([^/]+)}
77
+ ]
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
@@ -68,6 +68,14 @@ module Fleakr
68
68
 
69
69
  scoped_search
70
70
 
71
+ def self.user_id?(username_or_user_id)
72
+ (username_or_user_id =~ /^\d+@N\d{2}$/) ? true : false
73
+ end
74
+
75
+ def self.find_by_identifier(identifier)
76
+ user_id?(identifier) ? find_by_id(identifier) : find_by_username(identifier)
77
+ end
78
+
71
79
  # Is this a pro account?
72
80
  def pro?
73
81
  (self.pro.to_i == 0) ? false : true
@@ -1,3 +1,5 @@
1
1
  require 'fleakr/support/attribute'
2
2
  require 'fleakr/support/object'
3
- require 'fleakr/support/request'
3
+ require 'fleakr/support/request'
4
+ require 'fleakr/support/url_expander'
5
+ require 'fleakr/support/utility'
@@ -15,7 +15,7 @@ module Fleakr
15
15
 
16
16
  def split(source)
17
17
  location, attribute = source.split('@')
18
- location = self.name.to_s if location.blank?
18
+ location = self.name.to_s if Utility.blank?(location)
19
19
 
20
20
  [location, attribute]
21
21
  end
@@ -36,7 +36,7 @@ module Fleakr
36
36
  def value_from(document)
37
37
  values = sources.map do |source|
38
38
  node = node_for(document, source)
39
- [node.attributes[attribute(source)], node.inner_text].reject{|v| v.blank?}.first unless node.nil?
39
+ [node.attributes[attribute(source)], node.inner_text].reject{|v| Utility.blank?(v) }.first unless node.nil?
40
40
  end
41
41
  values.compact.first
42
42
  end
@@ -1,72 +1,76 @@
1
1
  module Fleakr
2
2
  module Support # :nodoc:all
3
3
  module Object
4
-
4
+
5
5
  module ClassMethods
6
-
6
+
7
7
  def attributes
8
8
  @attributes ||= []
9
9
  end
10
-
10
+
11
11
  def flickr_attribute(*names_and_options)
12
- options = names_and_options.extract_options!
12
+ attributes, options = Utility.extract_options(names_and_options)
13
13
 
14
- names_and_options.each do |name|
14
+ attributes.each do |name|
15
15
  self.attributes << Attribute.new(name, options[:from])
16
16
  class_eval "attr_accessor :#{name}"
17
17
  end
18
18
  end
19
-
19
+
20
20
  def has_many(*attributes)
21
21
  class_name = self.name
22
22
 
23
23
  attributes.each do |attribute|
24
- target = "Fleakr::Objects::#{attribute.to_s.classify}"
25
- finder_attribute = "#{class_name.demodulize.underscore}_id"
24
+ target = Utility.class_name_for('Fleakr::Objects', attribute)
25
+ finder_attribute = Utility.id_attribute_for(class_name)
26
26
 
27
27
  class_eval <<-CODE
28
- def #{attribute}
29
- @#{attribute} ||= #{target}.send("find_all_by_#{finder_attribute}".to_sym, self.id, self.authentication_options)
28
+ def #{attribute}(options = {})
29
+ options = authentication_options.merge(options)
30
+ sorted_options = options.sort {|a, b| a[0].to_s <=> b[0].to_s }
31
+ key = '#{attribute}_' + sorted_options.to_s
32
+
33
+ associations[key] ||= #{target}.send("find_all_by_#{finder_attribute}".to_sym, self.id, options)
30
34
  end
31
35
  CODE
32
36
  end
33
37
  end
34
-
38
+
35
39
  def find_all(condition, options)
36
40
  attribute = options[:using].nil? ? condition.to_s.sub(/^by_/, '') : options[:using]
37
41
  target_class = options[:class_name].nil? ? self.name : "Fleakr::Objects::#{options[:class_name]}"
38
-
42
+
39
43
  class_eval <<-CODE
40
44
  def self.find_all_#{condition}(value, options = {})
41
45
  options.merge!(:#{attribute} => value)
42
-
46
+
43
47
  response = Fleakr::Api::MethodRequest.with_response!('#{options[:call]}', options)
44
48
  (response.body/'rsp/#{options[:path]}').map {|e| #{target_class}.new(e, options) }
45
49
  end
46
50
  CODE
47
51
  end
48
-
52
+
49
53
  def find_one(condition, options)
50
54
  attribute = options[:using].nil? ? condition.to_s.sub(/^by_/, '') : options[:using]
51
-
55
+
52
56
  class_eval <<-CODE
53
57
  def self.find_#{condition}(value, options = {})
54
58
  options.merge!(:#{attribute} => value)
55
-
59
+
56
60
  response = Fleakr::Api::MethodRequest.with_response!('#{options[:call]}', options)
57
61
  #{self.name}.new(response.body, options)
58
62
  end
59
63
  CODE
60
64
  end
61
-
65
+
62
66
  def scoped_search
63
- key = "#{self.name.demodulize.underscore.downcase}_id".to_sym
67
+ key = Utility.id_attribute_for(self.name)
64
68
 
65
69
  class_eval <<-CODE
66
70
  def search(*parameters)
67
71
  options = {:#{key} => self.id}
68
72
  options.merge!(self.authentication_options)
69
-
73
+
70
74
  parameters << options
71
75
  Fleakr::Objects::Search.new(*parameters).results
72
76
  end
@@ -74,30 +78,30 @@ module Fleakr
74
78
  end
75
79
 
76
80
  def lazily_load(*attributes)
77
- options = attributes.extract_options!
81
+ attributes, options = Utility.extract_options(attributes)
78
82
 
79
83
  attributes.each do |attribute|
80
84
  class_eval <<-CODE
81
- def #{attribute}_with_loading
85
+ alias_method :#{attribute}_without_loading, :#{attribute}
86
+ def #{attribute}
82
87
  self.send(:#{options[:with]}) if @#{attribute}.nil?
83
88
  #{attribute}_without_loading
84
89
  end
85
- alias_method_chain :#{attribute}, :loading
86
90
  CODE
87
91
  end
88
92
  end
89
-
93
+
90
94
  end
91
-
95
+
92
96
  module InstanceMethods
93
-
97
+
94
98
  attr_reader :document, :authentication_options
95
-
99
+
96
100
  def initialize(document = nil, options = {})
97
101
  self.populate_from(document) unless document.nil?
98
102
  @authentication_options = options.extract!(:auth_token)
99
103
  end
100
-
104
+
101
105
  def populate_from(document)
102
106
  @document = document
103
107
  self.class.attributes.each do |attribute|
@@ -105,14 +109,25 @@ module Fleakr
105
109
  self.send("#{attribute.name}=".to_sym, value) unless value.nil?
106
110
  end
107
111
  end
108
-
112
+
113
+ def inspect
114
+ names = instance_variables.reject {|n| %w(@associations @document).include?(n.to_s) }
115
+ attributes = names.map {|n| "#{n}=#{instance_variable_get(n).inspect}" }
116
+
117
+ "#<#{self.class} #{attributes.join(', ')}>"
118
+ end
119
+
120
+ def associations
121
+ @associations ||= {}
122
+ end
123
+
109
124
  end
110
125
 
111
126
  def self.included(other)
112
127
  other.send(:extend, Fleakr::Support::Object::ClassMethods)
113
128
  other.send(:include, Fleakr::Support::Object::InstanceMethods)
114
129
  end
115
-
130
+
116
131
  end
117
132
  end
118
133
  end
@@ -0,0 +1,37 @@
1
+ module Fleakr
2
+ module Support # :nodoc:all
3
+
4
+ class UrlExpander
5
+
6
+ def self.expand(url)
7
+ new(url).expanded_path
8
+ end
9
+
10
+ def initialize(source_url)
11
+ @source_url = source_url
12
+ end
13
+
14
+ def path_to_expand
15
+ "/photo.gne?short=#{short_photo_id}"
16
+ end
17
+
18
+ def expanded_path
19
+ response_headers['location']
20
+ end
21
+
22
+ private
23
+
24
+ def response_headers
25
+ response = nil
26
+ Net::HTTP.start('www.flickr.com', 80) {|c| response = c.head(path_to_expand) }
27
+ response
28
+ end
29
+
30
+ def short_photo_id
31
+ @source_url.match(%r{([^/]+)$})[1]
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,66 @@
1
+ module Fleakr
2
+ module Support # :nodoc:
3
+
4
+ # = Utility
5
+ #
6
+ # Helpful utility methods.
7
+ #
8
+ class Utility
9
+
10
+ # Given a module name and an underscored name, generate the fully
11
+ # namespaced class name. For example:
12
+ #
13
+ # >> Utility.class_name_for('Fleakr::Api', 'method_request')
14
+ # => "Fleakr::Api::MethodRequest"
15
+ #
16
+ def self.class_name_for(module_name, name)
17
+ class_name = name.to_s.sub(/^(\w)/) {|m| m.upcase }
18
+ class_name = class_name.sub(/(_(\w))/) {|m| $2.upcase }
19
+ class_name = class_name.sub(/s$/, '')
20
+
21
+ "#{module_name}::#{class_name}"
22
+ end
23
+
24
+ # Given a class name as a string with an optional namespace, generate
25
+ # an attribute ID parameter suitable for retrieving the ID of an associated
26
+ # object. For example:
27
+ #
28
+ # >> Utility.id_attribute_for('Fleakr::Objects::Set')
29
+ # => "set_id"
30
+ #
31
+ def self.id_attribute_for(class_name)
32
+ class_name = class_name.match(/([^:]+)$/)[1]
33
+ class_name.gsub!(/([A-Z])([A-Z][a-z])/, '\1_\2')
34
+ class_name.gsub!(/([a-z])([A-Z])/, '\1_\2')
35
+
36
+ "#{class_name.downcase}_id"
37
+ end
38
+
39
+ # Determine if the passed value is blank. Blank values include nil,
40
+ # the empty string, and a string with only whitespace.
41
+ #
42
+ def self.blank?(object)
43
+ object.to_s.sub(/\s+/, '') == ''
44
+ end
45
+
46
+ # Extract the options from an array if present and return the new array
47
+ # and any available options. For example:
48
+ #
49
+ # >> Utility.extract_options([:sets, {:using => :key}])
50
+ # => [[:sets], {:using => :key}]
51
+ #
52
+ # Note that this method does not modify the supplied parameter.
53
+ #
54
+ def self.extract_options(array_with_possible_options)
55
+ array = array_with_possible_options.dup
56
+
57
+ options = array.pop if array.last.is_a?(Hash)
58
+ options ||= {}
59
+
60
+ [array, options]
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end