---
title: 'A Paperclip Refactoring Tale: Part One: Dependency Injection'
teaser: High level walk through of a paperclip feature refactor.
tags: web,paperclip,ruby,good code
author: Mike Burns
published_on: 2011-09-06
---

## The Beginnings

Good refactoring has a goal. The goal of this refactoring is to make it easier
for people to hook into [Paperclip](https://github.com/thoughtbot/paperclip).

To start I surveyed existing gems, plugins, and apps that hook into Paperclip. I
took a quick glance through Github and found that
[delayed_paperclip](https://github.com/jstorimer/delayed_paperclip),
[mongoid-paperclip](https://github.com/meskyanichi/mongoid-paperclip),
[papermill](https://github.com/bbenezech/papermill/),
[paperclip-extended](https://github.com/prashantkg16/paperclip-extended), and
[paperclip-facecrop](https://github.com/dagi3d/paperclip-facecrop) all served as
good sample gems that change Paperclip in unique ways. My end desire was to
remove the need for monkey patching, and if I could go one further to remove the
need for subclassing that'd be even better.

Papermill is a sample Rails app that uses Paperclip, extending it with
watermarking, copyrighting, and a custom geometry string. It does this by
extending the existing thumbnail Paperclip processor, overriding two methods:
the constructor and `transformation_command`. The new constructor does a ton of
parsing: the geometry string is parsed for options indicating a desire to add a
copyright or watermark, the copyright or watermark string is extracted from the
options, and cropping offsets are parsed out too. It uses all of this to
manipulate the `transformation_command`, like this: it invokes the `super`'s
`transformation_command`, which produces a string. It then `sub`s the string,
sticking various bits set from the constructor into the middle of the command.
The watermarking aspect is particularly tricky, since it must change the
`convert` command to a `convert ... | composite` command, using string
manipulation.

This will not do.

## The Abstractions

The first change I made was to parameterize the geometry parsing
([3f7aee3](https://github.com/thoughtbot/paperclip/commit/3f7aee300bcb1f2014904cf9df8224ece92ad2a9)
and
[eebc7d9](https://github.com/thoughtbot/paperclip/commit/eebc7d98a7ecafc11ac3e221c159169d9aab7d10)).
Now if you want a Thumbnail-based processor but with a custom geometry parser
you can do that right from `has_attached_file`:

```ruby
has_attached_file :avatar,
  :styles => {:medium => {
    :geometry => '80x60#10x5:2x1',
    :string_geometry_parser => CroppingGeometryCommandParser.new
  }}
```

This change looks a little like this. Before:

```ruby
module Paperclip
  class Thumbnail < Processor
    def initialize(file, options = {}, attachment = nil)
      super

      geometry             = options[:geometry] # this is not an option
      @file                = file
      @target_geometry     = Geometry.parse(geometry)
      @current_geometry    = Geometry.from_file(@file)
    end
  end
end
```

After:

```ruby
module Paperclip
  class Thumbnail < Processor
    def initialize(file, options = {}, attachment = nil)
      super

      geometry             = options[:geometry] # this is not an option
      @file                = file
      @target_geometry     = (options[:string_geometry_parser] || Geometry).parse(geometry)
      @current_geometry    = (options[:file_geometry_parser] || Geometry).from_file(@file)
    end
  end
end
```

The change? Instead of hardcoding the `Geometry` class it instead takes it as an
argument. The geometry parsing can now be replaced by any arbitrary object. In
the tests, for example, instead of creating files that parse to the proper size
or instead of stubbing, you can just pass an object that has a `#from_file`
method and produces the correct object. The `Geometry` class is still the
default for legacy reasons.

I split these into the `string_geometry_parser` and `file_geometry_parser`
because they do different tasks: one takes a file and produces an object that
knows how to produce the desired scaling and cropping arguments (lets call this
the transformation), and another produces an object that is passed to the
transformation.

With that small but powerful change out of the way I took a look at
paperclip-extended. This is an old gem; many of these extensions have been
rolled into Paperclip by now. The only thing that was impossible without monkey
patching was the extended interpolations. For example, normally you can have a
file path like `/system/images/:rails_env/:id/i:basename.:extension` and the
`:rails_env`, `:id`, `:basename`, and `:extension` will be replaced with proper
values&mdash;but it was impossible to add new interpolation values, such as
`:normalized_basename` (removing non-alphanumerics).

The fix for this
([7478455](https://github.com/thoughtbot/paperclip/commit/74784558f5a53d8ce46a4ac693e1e464c9dc4d73))
is similar to that for geometry parsing: pass a custom interpolator to the
`Attachment` through `has_attached_file`:

```ruby
has_attached_file :avatar,
  :interpolator => InterpolatorWithNormalization.new
```

This change takes place in the `Attachment` class. Before:

```ruby
module Paperclip
  class Attachment
    def interpolate(pattern, style_name = default_style)
      Paperclip::Interpolations.interpolate(pattern, self, style_name)
    end
  end
end
```

After:

```ruby
module Paperclip
  class Attachment
    def initialize(name, instance, options = {})
      @interpolator = (options[:interpolator] || Paperclip::Interpolations)
    end

    def interpolate(pattern, style_name = default_style)
      @interpolator.interpolate(pattern, self, style_name)
    end
  end
end
```

Again, a straight-forward change that makes this class easier to test and also
adds more extensibility.

## The Reasonings

This is dependency injection. It is a very boring and simple principle that goes
like this: abstract out class names where possible. This gains you both ease of
testing and straight-forward hooks for extensibility.

You just saw two examples of it from a real project, so I'll follow it up with
an example from nothing:

Before:

```ruby
class Car
  def gas
    puts "Vroom putter putter putter"
  end

  def brake
    puts "screeeetch!"
  end
end
```

After:

```ruby
class Car
  attr_accessor :engine

  def initialize(engine)
    @engine = engine
  end

  def gas
    engine.go!
  end

  def brake
    engine.stop!
  end
end
```

This gains you testing ease:

```ruby
describe Car do
  let(:engine) do
    Class.new do

      def go!
        @has_gone = true
      end

      def stop!
        @has_stopped = true
      end

      def has_gone?
        @has_gone
      end

      def has_stopped?
        @has_stopped
      end

    end.new
  end

  subject { Car.new(engine) }

  it "goes" do
    car.gas
    engine.should have_gone
  end

  it "stops" do
    car.brake
    engine.should have_stopped
  end
end
```

&hellip; and flexibility:

```ruby
yuppie = Person.new(:car => Car.new(HybridEngine.new(:diesel => 40, :electricity => 60))
bro = Person.new(:car => Car.new(V12Engine.new(:mpg => 2))
```

## Y'all

Now you get to use this. Things you can do:

* Write a Gem that provides a custom Paperclip interpolator. Encrypt file names,
  handle uppercase/lowercase issues, offer the time of day, offer a random
  number, and so on&mdash;anything you need!
* Write a gem that provides a custom Paperclip geometry sizer. It scales and
  crops images differently. For example, pick out people's faces, crop from the
  center, measure hard-to-measure images, and so on.
* While refactoring your code use a dependency injection principle somewhere.
  Instead of `Net::HTTP` use a passed-in object; same for any credit card
  processing, Tweeting, file-writing, logging, and so on.
