Class ActionController::Macros::ImageMagick::RecipeSet
In: actionpack/lib/action_controller/macros/image_magick.rb
Parent: Hash

Creating recipes

It is possible to write your own commands to use with imagemagick_tag. You do this by creating recipes. Your own recipes can have arguments and you can mix them with normal ImageMagick methods. Such as:

  <%= imagemagick_tag 'picture.jpg', 'customthumbnail+resize(100)+myborder(f00)' %>

There are multiple ways to write your own recipes: as a String of normal commands, as an inline method (Proc), as a method reference (Symbol) or as an external Class.

Recipe type: String

The easiest way is with a String. If you always render your profile images with ‘resize(100)+border(1,1,fff)’, you can shorten that with this recipe:

  # in the controller
  class UserController < ApplicationController
    imagemagick_for_directory '/var/lib/profiles'
    imagemagick_recipe :userprofile, 'resize(100)+border(1,1,fff)'
  end

  # in the view
  <%= imagemagick_tag 'john.jpg', 'userprofile' %>

As you see, you’ve now created your own ‘command’. userprofile will be expanded to resize(100)+border(1,1,fff) when rendering.

Recipe type: Proc

A more sophisticated method is by using a Proc. You can use this if you want more control than you can get with the String method. And if you need custom parameters for your recipes, you simply can’t use a String.

With a proc, adding a recipe works like this:

  # in the controller
  class UserController < ApplicationController
    imagemagick_for_directory '/var/lib/profiles'
    imagemagick_recipe :square, Proc.new { |image, size| image.resize!(size.to_i, size.to_i) }
  end

  # in the view
  <%= imagemagick_tag 'john.jpg', 'square(100)' %>

This example renders the image as a 100x100 pixel square. (If you look closely, you’ll notice that we’re passing the size of 100 pixel as a parameter to the recipe.) Proc-recipes work by accepting an image object and perhaps some parameters. In this case, the only parameter is size. The image object is the real RMagick::Image-object of the image that is currently being processed. In your proc, you can execute any of the RMagick methods you like.

There are two caveats you should keep in mind when using Proc recipes:

  • Except for the image object, all arguments passed to the Proc are Strings. The incoming parameter string is split by the commas, so 100,100 would give you two arguments. In your proc the "100" and "100" are +String+s, but most RMagick commands expect +Integer+s or +Float+s. Remember to convert them with to_i or to_f (as in the example above).
  • Your proc should return the image object. Not every RMagick method is available as an in-place method (which is destructive, with a trailing !). Thus, to make sure that the changes your recipe makes are saved, you should return the changed image. (When a destructive method is available, use that, but returning the image object is still required.)

Recipe type: Method or Class

If you don’t like providing the recipe as a Proc, you can also give your recipes the form of methods of the controller, or as a separate class.

If using a method, give the method name as a Symbol:

  class UserController < ApplicationController
    imagemagick_for_directory '/var/lib/profiles'
    imagemagick_recipe :ownresize, :my_own_resize_method

    private
      def my_own_resize_method(image, width, height)
        image.resize!(width.to_i, height.to_i)
      end
  end

If using a class, it should have a class method called execute_recipe_on. Provide the class to imagemagick_recipe:

  class MyResizeRecipe
    def self.execute_recipe_on(image, width, height)
      image.resize!(width.to_i, height.to_i)
    end
  end

  class UserController < ApplicationController
    imagemagick_for_directory '/var/lib/profiles'
    imagemagick_recipe :ownresize, MyResizeRecipe
  end

As for the arguments and the expected return, the method and class approaches are exactly the same as with a proc.

Recipe levels

The ImageMagick extension uses three different levels of recipes. Each level has it own RecipeSet, which is a Hash-like object that contains the recipes of that level.

At the deepest level, there are the BuiltinRecipes. Those recipes implement the standard commands that come with ImageMagick. These recipes are available in every controller. (You normally shouldn’t have to change the recipes at this level.)

At a little higher level, you have the GlobalRecipes. This is a list of recipes that are specific to the application, i.e. written by you. If you want your recipes to be available in every controller, you can add them to the GlobalRecipes RecipeSet.

At the highest level are the local recipes of a controller. These recipes are only available in one controller. Recipes defined with imagemagick_recipe are local recipes.

Defining the GlobalRecipes

Creating global recipes works a lot like creating routes. You should do it somewhere in your +environment.rb+, or in a file that is included by +environment.rb+. Example:

  ActionController::Macros::ImageMagick::GlobalRecipes.add do |recipes|
    recipes.add :customthumbnail, 'resize(100x100)'
    recipes.add :myborder, Proc.new { |image| image.border(1, 1, '#f00") }
  end

You can define your global recipes as a String, as a Proc or as a Class (see above). Defining them as a Method of the controller is not possible.

Defining local recipes

You can define local recipes in your controller with the imagemagick_recipe method. Example:

  class  < ApplicationController
    imagemagick_for '/var/lib/profiles'
    imagemagick_recipe :customthumbnail, 'resize(100x100)'
  end

You can define your local recipes as a String, as a Proc, as a Method or as a Class (see above).

It’s also possible to add local recipes on the fly. For example, if you have stored your recipes a database, you write a method to retrieve and add them. To do this, you can add the recipe-adding method as a filter for the imagemagick action. For example:

  class  < ApplicationController
    imagemagick_for '/var/lib/profiles'

    # add a filter that is executed before the imagemagick action
    before_filter :add_local_recipes, :only_action=>:imagemagick

    private
      def add_local_recipes
        imagemagick_local_recipes.add { |recipes|
          recipes.add :customthumbnail, 'resize(100x100)'
          recipes.add :myborder, Proc.new { |image| image.border(1, 1, '#f00") }
        end
      end
  end

Restricting recipes

It is possible to limit the commands that one can execute on an image. You can restrict it to only accept the recipes from the local recipe set, or only from the local and global recipe sets. Set this :max_recipe_level as an option for imagemagick_for:

  class  < ApplicationController
    # if you want only local recipes
    imagemagick_for '/var/lib/profiles', :max_recipe_level=>:local

    # if you want only local and global recipes
    imagemagick_for '/var/lib/profiles', :max_recipe_level=>:global
  end

If you don’t specify a :max_recipe_level, recipes from all three levels (:local, :global and :builtin) may be used.

Recipe versions

(This only applies if you’re using the cache.) Combining custom recipes with the image cache can lead to problems. In the cache, the images are stored with the names of the recipes they were generated with. Now, if you change a recipe but do not change the name, the cached images will not be updated. The extension will still serve images that were rendered with the old recipe.

The solution to this can be very simple: remember to empty the cache directory whenever you change your recipes. This is a perfect way to solve the problem. But there is another option, which is a little more sophisticated: add version numbers to your recipes, and update the number when you change the recipe.

Recipes can be numbers or strings. The order is not important (version 3 can be older than version 1). As long as you’re using new version numbers that you haven’t used before for the same recipe, you’ll be fine.

You can add the version number when you define the recipe. For example:

  class  < ApplicationController
    imagemagick_for '/var/lib/profiles', :cache=>'/my/cache'
    imagemagick_recipe :myborder, Proc.new({ |image| image.border(1, 1, '#f00") }), 'version1'
  end

The rendered images are now cached with the version number, ‘version1’. Now if you want to change the recipe, just be sure to bump the version number:

  class  < ApplicationController
    imagemagick_for '/var/lib/profiles', :cache=>'/my/cache'
    imagemagick_recipe :myborder, Proc.new({ |image| image.border(2, 2, '#f00") }), 'version2'
  end

That’s about all there is to recipe versions. Just two last notes:

  • If you don’t define a version number, the default is nil. This means that you can start setting version numbers starting with the second version of your recipe. (The first version has number nil, the second gets something like +’version2’+ etc.)
  • If your recipe is a String, you don’t have to bother adding version numbers. By default, the version number is set to the value of the String, which means that if you change the recipe String the version will be automatically updated too.

Methods

add   add_alias  

Public Instance methods

Adds a custom recipe with the given name and version. recipe can be a String, a Proc, a Class or a Symbol. If recipe is a String and the version is nil, version will be set to the String.

If a block is given, the block will be executed with this self as the only argument. (This can be used as a way to add many recipes in a row.)

Adds the command alias_name as an alias for the command original_name.

[Validate]