A gem in four parts.
This gem provides a Paperclip processor named :basic_thumbnailer
. You can use it like this:
gem 'paperclip-thumbnailer'
has_attached_file :avatar,
:styles => {:medium => '100x100'},
:processors => [:basic_thumbnailer]
It thumbnails things in the most boring way possible.
This gem is mainly for building other Paperclip processors. An example is the paperclip-watermarker processor.
The major building blocks are the Processor
, the filters, and the commands. Filters are composable classes which know how to build a command. The Processor
takes a bunch of filters and produces an object that Paperclip can use for post-processing.
Here's an example, which will produce what is essentially a no-op processor:
Processor.build_from([FilterTerminus.new(ConvertCommand.new)])
(It will actually pipe the file though the convert
program, but do nothing to it in the process.)
Here's a more complex example:
Processor.build_from(
[ThumbnailerFilter.new,
WatermarkerFilter.new,
FilterTerminus.new(
CommandCenter.new(
[ConvertCommand.new,
CompositeCommand.new]))])
This example will build a Paperclip processor that pipes a file through the convert
and composite
programs, with flags decided by the ThumbnailerFilter and WatermarkerFilter filters, in that order.
The PaperclipThumbnailer::Processor
class is a Paperclip processor. A Paperclip processor is any object that responds to the #make
method. The PaperclipThumbnailer::Processor
knows how to build a Paperclip processor from a bunch of filters.
The PaperclipThumbnailer::Processor.build_from
method takes a list of filters. The last element in the list must be a filter terminus; all other filters will chain atop that.
Let's take the complex example above:
Processor.build_from(
[ThumbnailerFilter.new,
WatermarkerFilter.new,
FilterTerminus.new(
CommandCenter.new(
[ConvertCommand.new,
CompositeCommand.new]))])
This will build a new PaperclipThumbnailer::Processor like this:
base_command = CommandCenter.new([ConvertCommand.new, CompositeCommand.new])
thumbnailer_filter = ThumbnailerFilter.new
watermarker_filter = WatermarkerFilter.new
terminus = FilterTerminus.new(base_command)
Processor.new(thumbnailer_filter.atop(watermarker_filter.atop(terminus)))
This new processor is ready to use as a Paperclip processor.
An individual filter knows how to do two things: stand atop another filter, and produce a command object. It is most likely and expected that you, the developer, will write a filter.
All filters except for terminus filters must define a method named #atop
:
class SepiaFilter
def atop(filter)
@filter = filter
end
end
This method is passed another filter, which may or may not be a terminus. This filter knows how to construct a command object which you can build upon.
class SepiaFilter
def command(source, destination, options)
@filter.command(source, destination, options).
for_command(:convert).
with_flag('sepia-tone', 20)
end
end
The #command
method must produce an object that responds to the #run!
method. We provide three useful examples of such command objects.
A command object is an object that responds to the #run!
method. To be most useful it must also respond to the following methods:
#for_command
#with_source
#with_destination
#with_configuration
#with_flag
All of the above commands must produce an object which will also respond to those commands.
As the simplest possible example:
class IdentityCommand
def run!
end
def for_command(tag)
self
end
def with_source(source)
self
end
def with_destination(destination)
self
end
def with_configuration(config)
self
end
def with_flag(flag, value = nil)
self
end
end
As a special case when making a command object that actually runs a command on the shell you should also define the #to_s
and #tag
methods. See the PaperclipThumbnailer::CommandCenter
class for examples of how those are used.
class IdentityCommand
def tag
:identity
end
def to_s
"echo"
end
end
You should unit test these filters and custom commands in the normal manner. Everything except #run!
produces a queryable object. We also provide some mock objects and shared examples to make this easier.
The paperclip-thumbnailer/specs
directory contains mock objects and shared examples.
To use the "a combinable filter" shared example you must set a subject to an instance of a filter with no base. It takes care of the rest:
describe SepiaFilter do
subject { SepiaFilter.new }
it_behaves_like "a combinable filter"
end
There is also an "a Paperclip processor" shared example which can be used to make sure that a given subject is a Paperclip processor. This is of questionable use.
The PaperclipThumbnailer::MockFilter
object can be used as a sample filter that your filter sits atop. Using it also gives you access to the have_flag
RSpec matcher, which works like this:
describe SepiaFilter do
let(:file) { 'file' }
let(:options) { {:a => 1} }
let(:filter) { SepiaFilter.new }
subject { filter.command(file, file, options) }
before { subject.atop(PaperclipThumbnailer::MockFilter.new) }
it "sets some stuff" do
subject.should have_flag(:resize).set_to('100x100').for_command(:convert)
subject.should have_configuration(:a, 1)
end
end
The PaperclipThumbnailer::MockCommand
object can be used as a base command. It is useful for testing a complete processor, for building a filter terminus, or for building command objects that wrap command objects.
We provide a basic PaperclipThumbnailer::FilterTerminus
class and a sample PaperclipThumbnailer::ThumbnailFilter
class. The provided filter terminus adds the source and destination to the command object. The thumbnail filter sets the resize flag for the convert
command.
We provide two ImageMagick-based command-line command objects, plus a class for piping command-line command objects together.
The PaperclipThumbnailer::ConvertCommand
and PaperclipThumbnailer::CompositeCommand
classes are wrappers for setting flags, sources, and destinations for the ImageMagick convert
and composite
commands, respectively.
The PaperclipThumbnailer::CommandCenter
class takes a list of command classes and chains them using the shell pipe, |
. For example:
CommandCenter.new([ConvertCommand.new, CompositeCommand.new])
You can set flags on specific commands using the #for_command
method and specifying the relevant tag.
command_center.for_command(:convert).with_flag(:resize, '100x100').for_command(:composite).with_flag(:sepia, 20)
Copyright 2011 thoughtbot. Licensed under the MIT license.
Original written by Mike Burns. For support please open a Github Issue.