Posts Tagged ‘functional programming’

Tip: Using Procs with Thoughtbot’s Paperclip

Thoughtbot’s Paperclip allows easy and reliable management of attachments. Using it is as simple as adding a few columns to your model and invoking the has_attached_file class method with the desired options.
One of Paperclip’s as-yet-undocumented features is the option to pass an anonymous function to the :url, :path, and :styles parameters of the has_attached_file class method.
In the example below, a Product has many ProductImages. No problem, except that I might not want every product image to have the same style attributes (e.g., a product could have a header image of ’200×400′ and a main image of ’300×300′). Certainly one way to handle this would be to create a distinct class for each type of product image (HeaderProductImage, MainProductImage, etc.).
But another, and perhaps more interesting solution, is to pass an anonymous function to the :styles parameter. That function can then return a customized style attribute for any kind of image:


class Product < ActiveRecord::Base
has_many :images, :class_name => "ProductImage"
end
class ProductImage < ActiveRecord::Base
belongs_to :product
has_attached_file :image,
:storage => :filesystem,
:path => ":rails_root/public/images/products/:attachment/:id/:style/:basename.:extension",
:url => "/images/products/:attachment/:id/:style/:basename.:extension",
# Because the :styles key can take a Proc object as an argument, we can specify the image
# styles dynamically based on the image's label.
:styles => Proc.new {|this|
defionition = ProductImageDefinitions.detect {|spec| spec[:label] == this.instance.label}
definition[:styles].merge(:thumb => '200x200>')
}
end


When Paperclip processes the attachment, it passes itself to the anonymous function. The model can then be accessed by calling the #instance attribute on the passed-in reference (see above).
The ProductImage class defines a #label attribute. The label acts as a key to a hash, ProductImageDefinitions, which specifies styles for various kinds of product images (you might define this somewhere in config/initializers).


ProductImagesDefinitions = {
'main' => {:styles => {:large => "1200x600!", :medium => "600x300!"},
'header' => {:styles => {:large => "400x600!", :medium => "200x300!"}
}


This allows for a high level of flexibility since adding a new kind of product image is simply a matter of modifying the ProductImageDefinitions hash. For a nice interface, you might write a module like this one to allow for the following manipulation of product images:


# Create product images.
@product.product_images.create(:label => 'main', :image => # Attachment object)
@product.product_images.create(:label => 'header', :image => # Attachment object)
# Access product images.
@product.available_images('main')
@product.available_images('header')


Not bad, right? The same kind of on-the-fly configuration can be applied to the :url and :path parameters. Thus does Paperclip’s support for anonymous functions enhance an already superb library.
For the record, I myself am still on the fence about whether this application of anonymous functions can be considered a good practice in terms of readability and maintainability.
But from the perspective of the sheer fun and practicality of the functional paradigm in Ruby, I’m going to consider it a worthwhile excursion.

Technology