Caching thumbnailer for middleman

In my ruby on rails projects I always use the dragonfly gem. It’s a great gem that gives you the desired flexibility to generate thumbnails on the fly, and not having to pregenerate the thumbnails aforehand.

Lately I have been tinkering around with middleman, a gem for compiling static sites, which really caught my eye. It’s written by the guys over at thoughtbot. It’s a great toolkit that gives you many of the tools from the ruby on rails stack, and from the padrino framework.

For generating thumbnails with dragonfly in middleman, I started using this gem: https://github.com/scarypine/middleman-dragonfly_thumbnailer

It’s a great extension, that allows you to display thumbnails seamlessly in development and production mode in middleman. It uses dragonfly and a similar syntax that ruby on rails developers will recognize from their normal workflow. Thanks to the developer I could use dragonfly together with middleman. The only thing that bugged me about this extension, is that it generates the thumbnails on every request (in development mode) and on every build (in production). That makes for slower requests in development and optionally a lengthy build process in production.

So i decided to clone this extension and build my own version, that keeps your thumbnails in a seperate directory. Caching them to disk to save time when generating the site in development or building in production. And when i say ‘caching’, of course I am NOT talking about http caching. Caching to disk.

It stores the thumbnails in the thumbs dir at the root of your project. If you disagree with this way of working, you can easily change that, by changing the thumbs_path function. When the thumbnail exists, it uses it, otherwise it is generated and saved to the thumbs dir. If you want to regenerate the thumbnails from scratch, you just delete the thumbnails in question from the thumbs dir. In development the raw b64 code is used in the src attribute of the images, and when building, the thumbs are copied to the build dir, and the src attribute uses the path to the image.

By reusing the thumbnails you save an incredible amount of time when building and the request become way faster in development. YAY! The only caveat is that - as mentioned above - you have to delete the thumbnails by hand, if you want to regenerate them. Seems like a good trade-off to me. And of course request or build will be slow because all thumbnails have to be generated.

NB: The extension uses the dragonfly gem, so you have to add this gem to your Gemfile.

To use the extension, put the code below in the file lib/caching_thumbnailer.rb. If the lib dir does not exist in your project, create it.

require 'dragonfly'

module Middleman
  module CachingThumbnailer
    class Extension < Middleman::Extension
      attr_accessor :images

      def initialize(app, options_hash = {}, &block)
        super
        @images = []
        configure_dragonfly
        FileUtils.mkdir(thumbs_path) unless File.directory?(thumbs_path)
      end

      def identifier(path, geometry)
        "#{path}@#{geometry}"
      end

      def absolute_source_path(path)
        File.join(app.config[:source], app.config[:images_dir], path)
      end

      def build_path(path, geometry)
        dir = File.dirname(path)
        subdir = geometry.gsub(/[^a-zA-Z0-9\-]/, '')
        File.join(dir, subdir, File.basename(path))
      end

      def thumbs_path
        "thumbs"
      end

      def absolute_thumb_path(path, geometry)
        File.join(thumbs_path, build_path(path, geometry))
      end

      def absolute_build_path(path, geometry)
        File.join(app.config[:build_dir], app.config[:images_dir], build_path(path, geometry))
      end

      def thumb(path, geometry)
        src_path = absolute_source_path(path)
        thumb_path = absolute_thumb_path(path, geometry)

        return unless File.exist?(src_path)

        unless File.exist?(thumb_path)
          image = ::Dragonfly.app.fetch_file(src_path)
          image = image.thumb(geometry)
          image.to_file(thumb_path).close
        else
          image = ::Dragonfly.app.fetch_file(thumb_path)
        end
        id = identifier(path, geometry)
        images << id unless images.include?(id)
        image
      end

      def after_build(builder)
        images.each do |image|
          path, geometry = image.split("@")
          src_path = absolute_thumb_path(path, geometry)
          dest_path = absolute_build_path(path, geometry)
          builder.say_status :create, dest_path
          dir_path = File.dirname(dest_path)
          FileUtils.mkdir_p(dir_path)
          FileUtils.copy(src_path, dest_path)
        end
      end

      helpers do
        def thumb_tag(path, geometry, options = {})
          image = extensions[:caching_thumbnailer].thumb(path, geometry)
          return unless image

          if environment == :development
            url = image.b64_data
          else
            url = extensions[:caching_thumbnailer].build_path(path, geometry)
          end

          image_tag(url, options)
        end
      end

      private

      def configure_dragonfly
        ::Dragonfly.app.configure do
          datastore :memory
          plugin :imagemagick
        end
      end
    end
  end
end

::Middleman::Extensions.register(
  :caching_thumbnailer,
  Middleman::CachingThumbnailer::Extension
)

Then in your config.rb add this line (at the top):

require 'lib/caching_thumbnailer'

and also this (to activate the extension):

activate :caching_thumbnailer

Have fun with middleman and dragonfly!

Comments

comments powered by Disqus