Caching Your Photographs

If you have at some point followed my now fairly ancient rFlickr tutorial you may have noticed that your photo page loads quite slowly, and that my photo page loads fairly quickly. To get my page to load as quickly as it does required a small custom caching method and a willingness on my part to sacrifice some bandwidth. Here’s how I did it.
This tutorial assumes that you have already worked through the previously mentioned rFlickr tutorial and have something similar to it set up. It also assumes that you have some knowledge of Rails, not that my knowledge was particularly wide ranging at the point I wrote this caching method.
First of all, you will need a table in your database to store the information about your photographs, I suggest the structure illustrated in the migration below;
class CreatePhotos < ActiveRecord::Migration
def self.up
create_table :photos, :id => false do |t|
t.column "flickr_id", :string, :limit => 25, :null => false
t.column "title", :string, :limit => 250
t.column "description", :text
t.column "url", :string, :limit => 250
end
add_index :photos, :flickr_id
end
def self.down
drop_table :photos
end
endOnce you have created this table you will need to create some folders to store the cached images, I created the following folders and will be using them throughout this tutorial;
#{RAILS_ROOT}/public/images/flickr_cache/small/
#{RAILS_ROOT}/public/images/flickr_cache/large/Then generate the model for this photos table;
$ cd /your/rails/application $ ./script/generate model Photo
Your view from the first tutorial can remain almost the same (details at the end of the post), however, to see the greatest speed improvement I suggest caching it, i.e;
<% cache do %>
... Your view code here. ...
<% end %>Then modify your view method in your photography controller to read something like;
def view
unless read_fragment({})
check_cache
@photos = Photo.find(:all)
end
endThe above code will make sure that a cached photography page doesn’t already exist, if it doesn’t, then and only then will it check that the photograph cache is up to date and query the database.
We have not yet created a ‘check_cache’ method, this method is the core method to make the photography page load much, much faster, even when the photography page’s cache does not exist. The method should be placed as the last method in your photography controller, the code is as follows;
private
def check_cache
if ENV['RAILS_ENV'] == 'production'
flickr = Flickr.new(RAILS_ROOT + "/config/flickr.cache", FLICKR_API_KEY, FLICKR_SHARED_SECRET)
@photos = flickr.people.getPublicPhotos(flickr.people.findByUsername(FLICKR_USERNAME))
@db_photos = Array.new
Photo.find(:all).each { |p| @db_photos.push(p.flickr_id) }
for photo in @photos.reverse
if !@db_photos.include?(photo.id)
db_photo = Photo.new
db_photo.flickr_id = photo.id.to_i
db_photo.title = photo.flickr.photos.getInfo(photo.id).title
db_photo.description = photo.flickr.photos.getInfo(photo.id).description
db_photo.url = photo.flickr.photos.getInfo(photo.id).urls.values[0]
db_photo.save
open(File.expand_path("#{RAILS_ROOT}/public/images/flickr_cache/small/" + photo.id + ".jpg"),"w").write(open(photo.url('s')).read)
open(File.expand_path("#{RAILS_ROOT}/public/images/flickr_cache/large/" + photo.id + ".jpg"),"w").write(open(photo.url).read)
end
end
end
endThe following code will only run if you are in production mode (and probably test mode too). It will then load the necessary information from Flickr using the methods outlined in the rFlickr tutorial post. The method then iterates through the collection of photos from Flickr in reverse, so they appear in the same order in the database as the order they appear on Flickr.
It will then check if the photo already exists in the database, if it does not it will store a copy of the photograph’s information in the database and download previews of the images from Flickr to your server, previews of images can then be loaded from your server rather than Flickr’s slow servers .
To take advantage of the cache you will also need to modify your view to access the thumbnails from your newly created local repository rather than from Flickr’s servers, i.e;
<% for photo in @photos.reverse %>
<%= image_tag("/images/flickr_cache/small/" + photo.flickr_id + ".jpg", :alt => photo.title) %>
<% end %>Hope this goes some way to helping you improve the speed of your website.
Check back soon.
Update: As I have just been reminded in the comments, I forgot a piece of code to make this tutorial work correctly. You should put the line;
require 'open-uri'Either in the bottom of you ‘environment.rb’ file or just under the ‘class’ line in the controller that is responsible for your photography page.
Update (3rd May 09): The code in the ‘check_cache’ method has been updated slightly, it now makes less round trips to the database, should speed things up if your DB server is in a different location to your application server. Also, any redirection problems you may have faced before should be solved by the new rFlickr ruby gem that has support for ‘farm’ URLs built in.



Bram - 24 Jun 2008 @ 14:40
Ran into a little problem. The script seems to get the correct URL in order to download the images from Flicr, but I get following error “No such file or directory – http://static.flickr.com/3064/2537719767_e9c30ec2b9_s.jpg”. When I paste the URL in my browser, I see the image but only after the URL redirected to farm4.static…
Is there a way to counter this redirection?
Alex - 25 Jun 2008 @ 10:44
I’ve had a look into the problem, it looks like an issue that has occurred due to the lack of maintenance of the rFlickr plugin with regards to updates of the Flickr API. I will look into fixing rFlickr or a least providing a patch to fix this problem over the next few weeks, I may have to improve my Ruby skills a little first though.
Roy Wright - 02 Jul 2008 @ 05:32
As a work-around, you can get the urls from getSizes. For example, to get the large image’s URL:
photo.flickr.photos.getSizes(photo.id)
p photo.sizes[:Large].url
Roy Wright - 02 Jul 2008 @ 05:38
Small correction. To get the image source you will need to use:
photo.sizes[:Large].source
Bram - 02 Jul 2008 @ 19:59
Thanks Roy, but I still get the following error:
No such file or directory – http://farm4.static.flickr.com/3128/2564745701_9bb4c40756_s.jpg
The link does work, but somehow the file doesn’t get opened for reading.
PS: I used :Square and :Medium as :Large doesn’t exist in the API as far as I’ve seen.
Bram - 02 Jul 2008 @ 20:06
Sorry, my bad. I forgot to include << require ‘open-uri’ >>
Works fine now. Cheers
Alex - 09 Aug 2008 @ 14:18
Thanks for reminding me about the include Bram, I had moved it in my configuration and hence had forgotten to mention it. It’s now been added to the tutorial.
Patrick - 21 Dec 2008 @ 12:08
Hi, I followed the original tutorial for simply displaying flickr images with rflickr with great success, but going through and modifying it to cache has thrown me. I have everything setup exactly as instructed but re-compacted the line wraps and deleted a couple of spaces to fix errors thrown at me. However, I’m left with a blank page load… the server log doesn’t note anything, it simply tells me it’s rendering the view and gives me the duration time… there’s no fetching of the images.
Separately, as I’m a newbie – is there a particular reason that there’s so so little documentation and tutorial material out there for Rails + Flickr API (rflickr or otherwise)?
Thanks