Login | Signup | Donate

Posted
30 May 2008 @ 23:03

Categories
Ruby on Rails, Tutorial

Author
Alex

Bookmark
Delicious, Digg, reddit, StumbleUpon

Caching Your Photographs

/images/post_images/rflickr.png

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
end

Once 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
end

The 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'] != 'development'
    flickr = Flickr.new(RAILS_ROOT + "/config/flickr.cache", \
        FLICKR_API_KEY, FLICKR_SHARED_SECRET)
    @photos = flickr.people.getPublicPhotos\
        (flickr.people.findByUsername(FLICKR_USERNAME))

      for photo in @photos.reverse
        if Photo.count(:all, :conditions => ["flickr_id = ?", photo.id]) == 0
          db_photo = Photo.new(:flickr_id => photo.id.to_i, :title => \
              photo.flickr.photos.getInfo(photo.id).title, :description => \
              photo.flickr.photos.getInfo(photo.id).description, :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
end

The 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.

Comments

Bram / menteb - 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 / admin - 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 / royw - 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 / royw - 02 Jul 2008 @ 05:38

Small correction. To get the image source you will need to use: photo.sizes[:Large].source

Bram / menteb - 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 / menteb - 02 Jul 2008 @ 20:06

Sorry, my bad. I forgot to include << require 'open-uri' >> Works fine now. Cheers

Alex / admin - 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.

Add A Comment

Please login or signup in order to post comments.