Papermill

  • Asset management made easy, 10 minutes integration.

  • All-you-can-eat glue around Polymorphic Paperclip table, SWFUpload & JQuery.

  • Associate any image or list of images with any model and any key.

Install the gem

sudo gem install papermill

Try the demo

rails -m http://github.com/bbenezech/papermill/raw/master/demo.txt papermill-example

Out-of-the-box compatibility with :

  • Formtastic # use :as => :[image|asset](s)_upload

  • JGrowl # for notifications (included)

  • FaceBox # for popups (included)

  • Stringex # (or any String#to_url) for asset filename/url generation

Navigator minimal requirements:

  • IE6+

  • Flash 9+

  • Javascript ON

Check your audience.

Server requirements:

  • Rails 2.3.

  • Paperclip 2.3.1.1 (loaded with gem dependency)

  • Front web server serving static assets if present, and forwarding demand to rails if not. Any classic installation will do that by default.

  • NOT compatible with Heroku/S3

Installation

Once gem is installed

Generate the migration

./script/generate papermill_table PapermillMigration

Edit it and migrate

rake db:migrate

Copy static assets to your public directory

./script/generate papermill_assets

Create the option file config/initializers/papermill.rb

./script/generate papermill_initializer

Go have a look at config/initializers/papermill.rb

In environment.rb

...
Rails::Initializer.run do |config|
  ...
  config.gem papermill
end

In your layout

Quick version

Inside <head></head>

<%= papermill_stylesheet_tag %>

Before </body> (best practice for javascript loading)

<%= papermill_javascript_tag :with_jquery => "no_conflict" %>

You don’t need :with_jquery if load it by yourself. Pass “no_conflict” if you use the default Prototype library, or some other ‘$’ library (mootools..)

In a real-world production application, you could use something like this, and adapt it to your own needs

Inside <head></head>

<% unless @content_for_papermill_inline_js.blank? %>
  <%= javascript_include_tag "/facebox/facebox.js", "/jgrowl/jquery.jgrowl_minimized.js", "/papermill/jquery.Jcrop.min.js", "/swfupload/swfupload.js", "/papermill/papermill.js", :cache => "papermill" %>
  <script type="text/javascript">
    jQuery(document).ready(function() {
      <%= yield :content_for_papermill_inline_js %>
    }
  </script>
<% end %>

Before </body>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js" type="text/javascript"></script>
<% unless @content_for_papermill_inline_js.blank? %>
  <%= stylesheet_link_tag("/facebox/facebox.css", "/jgrowl/jquery.jgrowl.css", "/Jcrop/jquery.Jcrop.css", "/papermill/papermill.css", :cache => "papermill") %>
  <style type="text/css">
    <%= yield :papermill_inline_css %>
  </style>
<% end %>

Security

URL-hacking

Maybe you don’t want users to use your application as a thumbnailing farm for their own uploaded images, or you have protected members areas and you don’t want users to ‘browse’ others members file.

  • Brute solution: pass :use_url_key to true in the options (config/initializers/papermill.rb). A crypted hash unique to your application and to each asset and to the requested style will be added to the URL. No more happy-guessing of anything. Do that first before going live, or you’ll have to migrate all assets…

  • pass :alias_only to true. This will disable the possibility to generate thumbnails with a papermill string in the url, but won’t do anything for the member area thing. Plus you will have to use aliases only, form helpers included (pass :thumbnail => { :style => :some_alias })

Usage

Assetable is the class that has_many papermill_assets (i.e. the class with the papermill declaration)

Assetable declaration

You can have one :default association (his settings will be used for unfound associations) and as many other associations as you want in your model. You can define a papermill relationship dynamically: just do smtg like Assetable.papermill(:dynamic_key, {}) when you need to. Perfect for CMS where associations are created by users. Then you’ll be able to use assetable.dynamic_key to retrieve the associated assets. If you don’t send the {}, default options from default association will be used, which may or may not be what you want.

Actually, the form helper leverages this when you use a :key that doesn’t exist: it will create a new Papermill relationship whith :key as the name and options from the :default declaration if any found on the model.

If you don’t need dynamic keys, just declare your associations in the model, like this :

class Article
  papermill :default
  papermill :images
  papermill :pdf_version
  papermill :cover_image
  papermill :illustrations
end

Form helpers

Example form:

form_for @assetable do 
  # I need a simple asset upload field :
  f.asset_upload  :pdf_version

  # Now I need to be able to upload as many documents as I need, and sort them at will
  # no document should be bigger than 1MB (respect the quoting!)
  # and I don't want the mass_edit feature
  f.assets_upload :documentation, :swfupload => { :file_size_limit => "'1 MB'" }, :mass_edit => false

  # I need to display *one* cover *image*, format will be 200x200
  # targetted_size will give the uploader hints when cropping the image after upload : desired display size and wanted aspect-ratio.
  # Better than cropping automatically in the center if the character's head is in the upper-left corner..
  # :thumbnail => { :width & :height } set the dimensions of the preview thumbnail
  # And finally, I need a 200x200# crop for preview, not the default 200x200> that would be generated by default ("#{:width}x#{:heigth}>")
  f.image_upload  :cover_image, :targetted_size => "200x200", :thumbnail => { :width => 200, :height => 200, :style => "200x200#" }

  # Now the image gallery, sortable.
  # I use :gallery => { :lines & :columns } to give the number of lines/columns, 
  # and some CSS will be generated to size the gallery perfectly, 
  # according to the thumb size inside the gallery and their padding/margin/border sizes.
  # the number of lines will increase if needed when uploading
  f.images_upload :illustrations, { 
    :thumbnail => {
      :width => 100,
      :height => 70
    },
    :gallery => {
      :columns => 8,       # number of columns
      :lines => 2,         # number of lines
      :vpadding => 2,      # vertical padding around each thumb
      :hpadding => 2,      # horizontal one
      :vmargin => 3,       # vertical margin
      :hmargin => 1,       # horizontal one 
      :border_thickness => 2 # border size around each thumb
    } 
  }
end

With Formtastic, pass

:as => (:image_upload | :images_upload | :asset_upload | :assets_upload)

And add your options as you would with the normal helpers.

With FormTagHelpers, use (image_upload_tag | images_upload_tag | asset_upload_tag | assets_upload_tag) @assetable, :key, options

image_upload_tag  @article, :cover_image, :targetted_size => "200x200"

Asset editing

  • double-click on any uploaded asset in any form-helper to access & edit his properties

  • then double-click image to crop it if it’s an image. You’ll then access a Jcrop window. Pass :targetted_size => “widthxheigth” to lock aspect-ratio and default the selection size to widthxheigth.

Thumbnails

On-the-fly request time processing:

PapermillAsset#url(papermill string (see 1.))  # path and url behave the same way
PapermillAsset#url(papermill alias (see 2.))

Pros: fast. Nothing done upon page rendering. If asset isn’t found by Apache/NGinx, then request is passed to rails, which will create it, once.

Cons: need to setup an alias in the options if you want to define use a hash instead of a papermill string (for custom watermark)

Render time processing:

PapermillAsset#url!(papermill string (see 1.))  # path! and url! behave the same way
PapermillAsset#url!(papermill alias (see 2.))
PapermillAsset#url!(papermill hash (see 3.))

Pros: can use a hash directly in the url call.

Cons: needs a thumbnail presence check at each render.

1. Papermill String

Consist of:

  • an ImageMagick geometry string (ex: “100x100>”, “original”, “100x#”, etc.)

  • an optional watermark (-wm) flag # will use option for URI

  • an optional copyright (©) flag # will use copyright text after the “©” or options

Examples:

image_tag @article.covers.first.url("100x100")
image_tag @article.covers.first.url("original©")
image_tag @article.covers.first.url("100x100#-wm©")
image_tag @article.covers.first.url("100x200#©papermill")

2. Papermill Alias

Those are application-wide, set them in the options

Consist of:

:geometry => "ImageMagick-geometry-string"
:copyright => true | "copyright"    # If true, the asset copyright field will be used. Edit the asset.
:watermark => true | URI            # If true, will use options[:watemark]

Examples:

#config/initilializers/papermill.rb

# snip
:aliases => {
  :thumb_copyrighted => {
    :geometry => "100x100",
    :copyright => "papermill",
  },
  :thumb_copyrighted_dynamically => {
    :geometry => "100x100",
    :copyright => true
  },
  :thumb_watermarked_with_rails => {
    :width => "100",
    :height => "100",
    :watermark => "/images/rails.png"
  }
}

Then in your views, simply do

image_tag @article.covers.first.url(:thumb_copyrighted)

3. Papermill Hash

Same as aliases, but defined directly in #url!() Plus you can add a :name that will be used for style-name (defaults to a md5 of the hash)

Example:

image_tag @article.covers.first.url(
  :geometry => "100x100",
  :watermark => "/images/rails.png",
  :copyright => "papermill",
  :name => "thumbnail_watermarked_and_copyrighted"
)

Resource access

Papermill generates an #<association_key> association

@entry.mug_shots.first
@entry.diaporamas.each do |image| ..
# etc.

Using PapermillAsset

@asset = @entry.mug_shots.first
image_tag @asset.url              # original
image_tag @asset.url("100x>")     # assuming asset is an image
image_tag @asset.url(:big)        # assuming you have a :big alias
@asset.name
@asset.content_type
@asset.path
@asset.path("100x>")
# etc.

Translations:

Papermill is fully I18n-able. Copy config/locales/papermill.yml to your root config/locale folder to modify any wording in a any locale.

Copyright © 2009 Benoit Bénézech, released under the MIT license