theoooo-fleakr 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/README.rdoc +350 -0
  2. data/Rakefile +41 -0
  3. data/lib/fleakr.rb +163 -0
  4. data/lib/fleakr/api.rb +8 -0
  5. data/lib/fleakr/api/file_parameter.rb +47 -0
  6. data/lib/fleakr/api/method_request.rb +66 -0
  7. data/lib/fleakr/api/option.rb +175 -0
  8. data/lib/fleakr/api/parameter.rb +35 -0
  9. data/lib/fleakr/api/parameter_list.rb +97 -0
  10. data/lib/fleakr/api/response.rb +35 -0
  11. data/lib/fleakr/api/upload_request.rb +75 -0
  12. data/lib/fleakr/api/value_parameter.rb +36 -0
  13. data/lib/fleakr/core_ext.rb +3 -0
  14. data/lib/fleakr/core_ext/false_class.rb +7 -0
  15. data/lib/fleakr/core_ext/hash.rb +22 -0
  16. data/lib/fleakr/core_ext/true_class.rb +7 -0
  17. data/lib/fleakr/objects.rb +12 -0
  18. data/lib/fleakr/objects/authentication_token.rb +60 -0
  19. data/lib/fleakr/objects/comment.rb +49 -0
  20. data/lib/fleakr/objects/contact.rb +31 -0
  21. data/lib/fleakr/objects/error.rb +22 -0
  22. data/lib/fleakr/objects/group.rb +36 -0
  23. data/lib/fleakr/objects/image.rb +50 -0
  24. data/lib/fleakr/objects/photo.rb +147 -0
  25. data/lib/fleakr/objects/photo_context.rb +49 -0
  26. data/lib/fleakr/objects/search.rb +30 -0
  27. data/lib/fleakr/objects/set.rb +50 -0
  28. data/lib/fleakr/objects/tag.rb +56 -0
  29. data/lib/fleakr/objects/user.rb +95 -0
  30. data/lib/fleakr/support.rb +2 -0
  31. data/lib/fleakr/support/attribute.rb +46 -0
  32. data/lib/fleakr/support/object.rb +110 -0
  33. data/lib/fleakr/version.rb +13 -0
  34. data/test/fixtures/auth.checkToken.xml +8 -0
  35. data/test/fixtures/auth.getFullToken.xml +8 -0
  36. data/test/fixtures/auth.getToken.xml +8 -0
  37. data/test/fixtures/contacts.getPublicList.xml +7 -0
  38. data/test/fixtures/groups.pools.getPhotos.xml +7 -0
  39. data/test/fixtures/people.findByEmail.xml +6 -0
  40. data/test/fixtures/people.findByUsername.xml +6 -0
  41. data/test/fixtures/people.getInfo.xml +18 -0
  42. data/test/fixtures/people.getPublicGroups.xml +7 -0
  43. data/test/fixtures/people.getPublicPhotos.xml +7 -0
  44. data/test/fixtures/photos.comments.getList.xml +7 -0
  45. data/test/fixtures/photos.getContext.xml +6 -0
  46. data/test/fixtures/photos.getInfo.xml +20 -0
  47. data/test/fixtures/photos.getSizes.xml +10 -0
  48. data/test/fixtures/photos.search.xml +7 -0
  49. data/test/fixtures/photosets.comments.getList.xml +7 -0
  50. data/test/fixtures/photosets.getList.xml +13 -0
  51. data/test/fixtures/photosets.getPhotos.xml +7 -0
  52. data/test/fixtures/tags.getListPhoto.xml +9 -0
  53. data/test/fixtures/tags.getListUser.xml +10 -0
  54. data/test/fixtures/tags.getRelated.xml +9 -0
  55. data/test/test_helper.rb +140 -0
  56. data/test/unit/fleakr/api/file_parameter_test.rb +63 -0
  57. data/test/unit/fleakr/api/method_request_test.rb +93 -0
  58. data/test/unit/fleakr/api/option_test.rb +179 -0
  59. data/test/unit/fleakr/api/parameter_list_test.rb +176 -0
  60. data/test/unit/fleakr/api/parameter_test.rb +34 -0
  61. data/test/unit/fleakr/api/response_test.rb +49 -0
  62. data/test/unit/fleakr/api/upload_request_test.rb +145 -0
  63. data/test/unit/fleakr/api/value_parameter_test.rb +41 -0
  64. data/test/unit/fleakr/core_ext/false_class_test.rb +13 -0
  65. data/test/unit/fleakr/core_ext/hash_test.rb +32 -0
  66. data/test/unit/fleakr/core_ext/true_class_test.rb +13 -0
  67. data/test/unit/fleakr/objects/authentication_token_test.rb +61 -0
  68. data/test/unit/fleakr/objects/comment_test.rb +66 -0
  69. data/test/unit/fleakr/objects/contact_test.rb +58 -0
  70. data/test/unit/fleakr/objects/error_test.rb +21 -0
  71. data/test/unit/fleakr/objects/group_test.rb +46 -0
  72. data/test/unit/fleakr/objects/image_test.rb +81 -0
  73. data/test/unit/fleakr/objects/photo_context_test.rb +80 -0
  74. data/test/unit/fleakr/objects/photo_test.rb +243 -0
  75. data/test/unit/fleakr/objects/search_test.rb +74 -0
  76. data/test/unit/fleakr/objects/set_test.rb +82 -0
  77. data/test/unit/fleakr/objects/tag_test.rb +98 -0
  78. data/test/unit/fleakr/objects/user_test.rb +91 -0
  79. data/test/unit/fleakr/support/attribute_test.rb +126 -0
  80. data/test/unit/fleakr/support/object_test.rb +129 -0
  81. data/test/unit/fleakr_test.rb +171 -0
  82. metadata +175 -0
@@ -0,0 +1,75 @@
1
+ module Fleakr
2
+ module Api # :nodoc:
3
+
4
+ # = UploadRequest
5
+ #
6
+ # This implements the upload functionality of the Flickr API which is needed
7
+ # to create new photos and replace the photo content of existing photos
8
+ #
9
+ class UploadRequest
10
+
11
+ ENDPOINT_URIS = {
12
+ :create => 'http://api.flickr.com/services/upload/',
13
+ :update => 'http://api.flickr.com/services/replace/'
14
+ }
15
+
16
+ attr_reader :parameters, :type
17
+
18
+ # Send a request and return a Response object. If an API error occurs, this raises
19
+ # a Fleakr::ApiError with the reason for the error. See UploadRequest#new for more
20
+ # details.
21
+ #
22
+ def self.with_response!(filename, type = :create, options = {})
23
+ request = self.new(filename, type, options)
24
+ response = request.send
25
+
26
+ raise(Fleakr::ApiError, "Code: #{response.error.code} - #{response.error.message}") if response.error?
27
+
28
+ response
29
+ end
30
+
31
+ # Create a new UploadRequest with the specified filename, type, and options. Type
32
+ # is one of <tt>:create</tt> or <tt>:update</tt> to specify whether we are saving a new
33
+ # image or replacing an existing one.
34
+ #
35
+ # For a list of available options, see the documentation in Fleakr::Objects::Photo
36
+ #
37
+ def initialize(filename, type = :create, options = {})
38
+ @type = type
39
+ @options = options
40
+
41
+ @parameters = ParameterList.new(upload_options)
42
+ @parameters << FileParameter.new('photo', filename)
43
+ end
44
+
45
+ # A list of upload options for this upload request (see Fleakr::Api::Option)
46
+ #
47
+ def upload_options
48
+ option_list = @options.map {|key, value| Option.for(key, value) }
49
+ option_list.inject({}) {|hash, option| hash.merge(option.to_hash)}
50
+ end
51
+
52
+ def headers # :nodoc:
53
+ {'Content-Type' => "multipart/form-data; boundary=#{self.parameters.boundary}"}
54
+ end
55
+
56
+ def send # :nodoc:
57
+ response = Net::HTTP.start(endpoint_uri.host, endpoint_uri.port) do |http|
58
+ logger.info("Sending upload request to: #{endpoint_uri}")
59
+ logger.debug("Request data:\n#{self.parameters.to_form}")
60
+ logger.debug("Request headers:\n#{self.headers.inspect}")
61
+
62
+ http.post(endpoint_uri.path, self.parameters.to_form, self.headers)
63
+ end
64
+ logger.debug("Response data:\n#{response.body}")
65
+ Response.new(response.body)
66
+ end
67
+
68
+ private
69
+ def endpoint_uri
70
+ @endpoint_uri ||= URI.parse(ENDPOINT_URIS[self.type])
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,36 @@
1
+ module Fleakr
2
+ module Api # :nodoc:
3
+
4
+ # = ValueParameter
5
+ #
6
+ # A simple name / value parameter for use in API calls
7
+ #
8
+ class ValueParameter < Parameter
9
+
10
+ attr_reader :value
11
+
12
+ # Create a new parameter with the specified name / value pair.
13
+ #
14
+ def initialize(name, value, include_in_signature = true)
15
+ @value = value
16
+ super(name, include_in_signature)
17
+ end
18
+
19
+ # Generate the query string representation of this parameter.
20
+ #
21
+ def to_query
22
+ "#{self.name}=#{CGI.escape(self.value.to_s)}"
23
+ end
24
+
25
+ # Generate the form representation of this parameter.
26
+ #
27
+ def to_form
28
+ "Content-Disposition: form-data; name=\"#{self.name}\"\r\n" +
29
+ "\r\n" +
30
+ "#{self.value}\r\n"
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ require 'fleakr/core_ext/hash'
2
+ require 'fleakr/core_ext/false_class'
3
+ require 'fleakr/core_ext/true_class'
@@ -0,0 +1,7 @@
1
+ class FalseClass # :nodoc:
2
+
3
+ def to_i
4
+ 0
5
+ end
6
+
7
+ end
@@ -0,0 +1,22 @@
1
+ class Hash
2
+
3
+ # Extract the matching keys from the source hash and return
4
+ # a new hash with those keys:
5
+ #
6
+ # >> h = {:a => 'b', :c => 'd'}
7
+ # => {:a=>"b", :c=>"d"}
8
+ # >> h.extract!(:a)
9
+ # => {:a=>"b"}
10
+ # >> h
11
+ # => {:c=>"d"}
12
+ #
13
+ def extract!(*keys)
14
+ value = {}
15
+
16
+ keys.each {|k| value.merge!({k => self[k]}) if self.has_key?(k) }
17
+ keys.each {|k| delete(k) }
18
+
19
+ value
20
+ end
21
+
22
+ end
@@ -0,0 +1,7 @@
1
+ class TrueClass # :nodoc:
2
+
3
+ def to_i
4
+ 1
5
+ end
6
+
7
+ end
@@ -0,0 +1,12 @@
1
+ require 'fleakr/objects/authentication_token'
2
+ require 'fleakr/objects/contact'
3
+ require 'fleakr/objects/error'
4
+ require 'fleakr/objects/group'
5
+ require 'fleakr/objects/image'
6
+ require 'fleakr/objects/photo_context'
7
+ require 'fleakr/objects/photo'
8
+ require 'fleakr/objects/comment'
9
+ require 'fleakr/objects/tag'
10
+ require 'fleakr/objects/search'
11
+ require 'fleakr/objects/set'
12
+ require 'fleakr/objects/user'
@@ -0,0 +1,60 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+
4
+ # = AuthenticationToken
5
+ #
6
+ # This class represents an authentication token used for API calls that
7
+ # require authentication before they can be used
8
+ #
9
+ # == Attributes
10
+ #
11
+ # [value] The token value that is used in subsequent API calls
12
+ # [permissions] The permissions granted to this application (read / write / delete)
13
+ #
14
+ class AuthenticationToken
15
+
16
+ include Fleakr::Support::Object
17
+
18
+ flickr_attribute :value, :from => 'auth/token'
19
+ flickr_attribute :permissions, :from => 'auth/perms'
20
+ flickr_attribute :user_id, :from => 'auth/user@nsid'
21
+ flickr_attribute :user_name, :from => 'auth/user@username'
22
+ flickr_attribute :full_name, :from => 'auth/user@fullname'
23
+
24
+ # Retrieve a full authentication token from the supplied mini-token (e.g. 123-456-789)
25
+ #
26
+ def self.from_mini_token(mini_token)
27
+ from :mini_token, mini_token
28
+ end
29
+
30
+ # Retrieve a full authentication token from the supplied auth_token string
31
+ # (e.g. 45-76598454353455)
32
+ #
33
+ def self.from_auth_token(auth_token)
34
+ from :auth_token, auth_token
35
+ end
36
+
37
+ # Retrieve a full authentication token from the supplied frob
38
+ def self.from_frob(frob)
39
+ from :frob, frob
40
+ end
41
+
42
+ def self.from(thing, value) # :nodoc:
43
+ api_methods = {
44
+ :mini_token => 'getFullToken',
45
+ :auth_token => 'checkToken',
46
+ :frob => 'getToken'
47
+ }
48
+
49
+ method = "auth.#{api_methods[thing]}"
50
+
51
+ parameters = {thing => value, :authenticate? => false}
52
+ response = Fleakr::Api::MethodRequest.with_response!(method, parameters)
53
+
54
+ self.new(response.body)
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,49 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+
4
+ # = Comment
5
+ #
6
+ # This class represents a comment that can be associated with a single photo or an
7
+ # entire photoset.
8
+ #
9
+ # == Attributes
10
+ #
11
+ # [id] The unique identifier for this comment
12
+ # [url] The direct URL / permalink to reference this comment
13
+ # [body] The comment itself - also available with <tt>to_s</tt>
14
+ #
15
+ class Comment
16
+
17
+ include Fleakr::Support::Object
18
+
19
+ find_all :by_photo_id, :call => 'photos.comments.getList', :path => 'comments/comment'
20
+ find_all :by_set_id, :using => :photoset_id, :call => 'photosets.comments.getList', :path => 'comments/comment'
21
+
22
+ flickr_attribute :id
23
+ flickr_attribute :author_id, :from => '@author'
24
+ flickr_attribute :created, :from => '@datecreate'
25
+ flickr_attribute :url, :from => '@permalink'
26
+ flickr_attribute :body, :from => '.'
27
+
28
+ # The user who supplied the comment. See Fleakr::Objects::User for more information
29
+ #
30
+ def author
31
+ @author ||= User.find_by_id(author_id)
32
+ end
33
+
34
+ # When was this comment created?
35
+ #
36
+ def created_at
37
+ Time.at(created.to_i)
38
+ end
39
+
40
+ # The contents of the comment - also available as <tt>body</tt>
41
+ #
42
+ def to_s
43
+ body
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+ class Contact # :nodoc:
4
+
5
+ include Fleakr::Support::Object
6
+
7
+ flickr_attribute :id, :from => '@nsid'
8
+ flickr_attribute :username
9
+ flickr_attribute :icon_server, :from => '@iconserver'
10
+ flickr_attribute :icon_farm, :from => '@iconfarm'
11
+
12
+ # Retrieve a list of contacts for the specified user ID and return an initialized
13
+ # collection of #User objects
14
+ def self.find_all_by_user_id(user_id)
15
+ response = Fleakr::Api::MethodRequest.with_response!('contacts.getPublicList', :user_id => user_id)
16
+ (response.body/'contacts/contact').map {|c| Contact.new(c).to_user }
17
+ end
18
+
19
+ def to_user
20
+ user = User.new
21
+ self.class.attributes.each do |attribute|
22
+ attribute_name = attribute.name
23
+ user.send("#{attribute.name}=".to_sym, self.send(attribute.name))
24
+ end
25
+
26
+ user
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+ # = Error
4
+ #
5
+ # == Accessors
6
+ #
7
+ # This class is a simple wrapper for the error response that the API returns. There are
8
+ # a couple of attributes:
9
+ #
10
+ # [code] The error code as described in the documentation
11
+ # [message] The associated error message
12
+ #
13
+ class Error
14
+
15
+ include Fleakr::Support::Object
16
+
17
+ flickr_attribute :code, :from => 'err@code'
18
+ flickr_attribute :message, :from => 'err@msg'
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+
4
+ # = Group
5
+ #
6
+ # == Accessors
7
+ #
8
+ # [id] This group's ID
9
+ # [name] The name of the group
10
+ #
11
+ # == Associations
12
+ #
13
+ # [photos] The photos that are in this group
14
+ #
15
+ class Group
16
+
17
+ include Fleakr::Support::Object
18
+
19
+ flickr_attribute :id, :from => '@nsid'
20
+ flickr_attribute :name
21
+ flickr_attribute :adult_flag, :from => '@eighteenplus'
22
+
23
+ find_all :by_user_id, :call => 'people.getPublicGroups', :path => 'groups/group'
24
+
25
+ has_many :photos
26
+
27
+ scoped_search
28
+
29
+ # Is this group adult-only? (e.g. only 18+ allowed to view)
30
+ def adult?
31
+ (adult_flag == '1')
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ module Fleakr
2
+ module Objects
3
+
4
+ # = Image
5
+ #
6
+ # This class wraps the functionality for saving remote images to disk. It's called
7
+ # by the Fleakr::Objects::Photo class to save an image with a specific size and would
8
+ # typically never be called directly.
9
+ #
10
+ # Example:
11
+ #
12
+ # user = Fleakr.user('brownout')
13
+ # user.photos.first.small.save_to('/tmp')
14
+ #
15
+ # == Attributes
16
+ #
17
+ # [size] The name of this image's size (e.g. Square, Large, etc...)
18
+ # [width] The width of this image
19
+ # [height] The height of this image
20
+ # [url] The direct URL for this image
21
+ # [page] The page on Flickr that represents this photo
22
+ #
23
+ class Image
24
+
25
+ include Fleakr::Support::Object
26
+
27
+ flickr_attribute :width, :height
28
+ flickr_attribute :size, :from => '@label'
29
+ flickr_attribute :url, :from => '@source'
30
+ flickr_attribute :page, :from => '@url'
31
+
32
+ find_all :by_photo_id, :call => 'photos.getSizes', :path => 'sizes/size'
33
+
34
+ # The filename portion of the image (without the full URL)
35
+ def filename
36
+ self.url.match(/([^\/]+)$/)[1]
37
+ end
38
+
39
+ # Save this image to the specified directory or file. If the target is a
40
+ # directory, the file will be created with the original filename from Flickr.
41
+ # If the target is a file, it will be saved with the specified name. In the
42
+ # case that the target file already exists, this method will overwrite it.
43
+ def save_to(target, prefix = nil)
44
+ destination = File.directory?(target) ? "#{target}/#{prefix}#{self.filename}" : "#{target}"
45
+ File.open(destination, 'w') {|f| f << Net::HTTP.get(URI.parse(self.url)) }
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,147 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+
4
+ # = Photo
5
+ #
6
+ # Handles both the retrieval of Photo objects from various associations (e.g. User / Set) as
7
+ # well as the ability to upload images to the Flickr site.
8
+ #
9
+ # == Attributes
10
+ #
11
+ # [id] The ID for this photo
12
+ # [title] The title of this photo
13
+ # [description] The description of this photo
14
+ # [secret] This photo's secret (used for sharing photo without permissions checking)
15
+ # [comment_count] Count of the comments attached to this photo
16
+ # [url] This photo's page on Flickr
17
+ # [square] The tiny square representation of this photo
18
+ # [thumbnail] The thumbnail for this photo
19
+ # [small] The small representation of this photo
20
+ # [medium] The medium representation of this photo
21
+ # [large] The large representation of this photo
22
+ # [original] The original photo
23
+ # [previous] The previous photo based on the current context
24
+ # [next] The next photo based on the current context
25
+ #
26
+ # == Associations
27
+ #
28
+ # [images] The underlying images for this photo.
29
+ # [tags] The tags for this photo.
30
+ # [comments] The comments associated with this photo
31
+ #
32
+ class Photo
33
+
34
+ # Available sizes for this photo
35
+ SIZES = [:square, :thumbnail, :small, :medium, :large, :original]
36
+
37
+ include Fleakr::Support::Object
38
+ extend Forwardable
39
+
40
+ def_delegators :context, :next, :previous
41
+
42
+ flickr_attribute :id, :from => ['@id', 'photoid']
43
+ flickr_attribute :title, :description, :secret, :posted, :taken, :url
44
+ flickr_attribute :farm_id, :from => '@farm'
45
+ flickr_attribute :server_id, :from => '@server'
46
+ flickr_attribute :owner_id, :from => ['@owner', 'owner@nsid']
47
+ flickr_attribute :updated, :from => '@lastupdate'
48
+ flickr_attribute :comment_count, :from => 'comments'
49
+
50
+ # TODO:
51
+ # * visibility
52
+ # * editability
53
+ # * usage
54
+
55
+ find_all :by_set_id, :using => :photoset_id, :call => 'photosets.getPhotos', :path => 'photoset/photo'
56
+ find_all :by_user_id, :call => 'people.getPublicPhotos', :path => 'photos/photo'
57
+ find_all :by_group_id, :call => 'groups.pools.getPhotos', :path => 'photos/photo'
58
+
59
+ find_one :by_id, :using => :photo_id, :call => 'photos.getInfo'
60
+
61
+ lazily_load :posted, :taken, :updated, :comment_count, :url, :description, :with => :load_info
62
+
63
+ has_many :images, :tags, :comments
64
+
65
+ # Upload the photo specified by <tt>filename</tt> to the user's Flickr account. When uploading,
66
+ # there are several options available (none are required):
67
+ #
68
+ # [:title] The title for this photo. Any string is allowed.
69
+ # [:description] The description for this photo. Any string is allowed.
70
+ # [:tags] A collection of tags for this photo. This can be a string or array of strings.
71
+ # [:viewable_by] Who can view this photo? Acceptable values are one of <tt>:everyone</tt>,
72
+ # <tt>:friends</tt> or <tt>:family</tt>. This can also take an array of values
73
+ # (e.g. <tt>[:friends, :family]</tt>) to make it viewable by friends and family.
74
+ # [:level] The safety level of this photo. Acceptable values are one of <tt>:safe</tt>,
75
+ # <tt>:moderate</tt>, or <tt>:restricted</tt>.
76
+ # [:type] The type of image this is. Acceptable values are one of <tt>:photo</tt>,
77
+ # <tt>:screenshot</tt>, or <tt>:other</tt>.
78
+ # [:hide?] Should this photo be hidden from public searches? Takes a boolean.
79
+ #
80
+ def self.upload(filename, options = {})
81
+ response = Fleakr::Api::UploadRequest.with_response!(filename, :create, options)
82
+ photo = Photo.new(response.body)
83
+ Photo.find_by_id(photo.id)
84
+ end
85
+
86
+ # Replace the current photo's image with the one specified by filename. This
87
+ # call requires authentication.
88
+ #
89
+ def replace_with(filename)
90
+ response = Fleakr::Api::UploadRequest.with_response!(filename, :update, :photo_id => self.id)
91
+ self.populate_from(response.body)
92
+ self
93
+ end
94
+
95
+ # TODO: Refactor this to remove duplication w/ User#load_info - possibly in the lazily_load class method
96
+ def load_info # :nodoc:
97
+ response = Fleakr::Api::MethodRequest.with_response!('photos.getInfo', :photo_id => self.id)
98
+ self.populate_from(response.body)
99
+ end
100
+
101
+ def context # :nodoc:
102
+ @context ||= begin
103
+ response = Fleakr::Api::MethodRequest.with_response!('photos.getContext', :photo_id => self.id)
104
+ PhotoContext.new(response.body)
105
+ end
106
+ end
107
+
108
+ # The user who uploaded this photo. See Fleakr::Objects::User for additional information.
109
+ #
110
+ def owner
111
+ @owner ||= User.find_by_id(owner_id)
112
+ end
113
+
114
+ # When was this photo posted?
115
+ #
116
+ def posted_at
117
+ Time.at(posted.to_i)
118
+ end
119
+
120
+ # When was this photo taken?
121
+ #
122
+ def taken_at
123
+ Time.parse(taken)
124
+ end
125
+
126
+ # When was this photo last updated? This includes addition of tags and other metadata.
127
+ #
128
+ def updated_at
129
+ Time.at(updated.to_i)
130
+ end
131
+
132
+ # Create methods to access image sizes by name
133
+ SIZES.each do |size|
134
+ define_method(size) do
135
+ images_by_size[size]
136
+ end
137
+ end
138
+
139
+ private
140
+ def images_by_size
141
+ image_sizes = SIZES.inject({}) {|l,o| l.merge(o => nil)}
142
+ self.images.inject(image_sizes) {|l,o| l.merge!(o.size.downcase.to_sym => o) }
143
+ end
144
+
145
+ end
146
+ end
147
+ end