Skip to content
Simple file uploads for Active Record models
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
db/migrate
lib
.gitignore
LICENSE
README.md
attach.gemspec

README.md

Attach

Attach allow you to attach files/images/documents to Active Record models with ease. Just define which attachments you wish to add and you can easily upload them to your server (either file system or database).

Installation

In order to get started, add the gem to your Gemfile:

gem 'attach'

Once included, add the database table which will store your attachments.

$ rake attach:install:migrations
$ rake db:migrate

Getting started

You can define the attachments which you wish to use on any of your models as such:

class Person < ActiveRecord::Base
  attachment :cover_photo
  attachment :profile_picture
end

Uploading attachments

You'll have a reader and a writer for the attachment that you've created which allows you to set the file to be uploaded. For example:

person = Person.new

# Set the photo from some data you have
person.cover_photo = some_binary_data
person.cover_photo.file_name = "cover-photo.jpg"
person.cover_photo.file_type = "image/jpg"

# Or you can pass in an ActionDispatch::Http::UploadedFile
person.cover_photo = params[:person][:cover_photo]

# You can also pass a pre-constructed `Attach::File` object
file = Attach::File.new(binary)
file.name = "some-name.pdf"
file.type = "application/pdf"
person.cover_photo = file

It's worth noting that calling your reader will always return an Attach::Attachment object regardless of what you pass in. If you pass in an uploaded file it will be converted to the Attach::Attachment object at the point it is set.

Uploading from a form

<% form_for @person, :html => {:multipart => true} do |f| %>
  <%= f.file_field :profile_picture %>
  <%= f.file_field :cover_photo %>
  <%= f.submit "Upload Attachments" %>
<% end %>

Accessing attachments

You can access any of your attachments easily through the methods as shown below.

# Accessing attachments
person = Person.find(person.id)
person.cover_photo            #=> Attach::Attachment
person.cover_photo.url        #=> "/attachment/145d17ed-d5e3-4b55-8c89-ecad9521ad73/snom-mm2.jpg"
person.cover_photo.file_name  #=> "snom-mm2.jpg"
person.cover_photo.digest     #=> "c4de7fd75a7e2ec37bde3a5ef9fa53a1ce9228c0"
person.cover_photo.binary     #=> <Binary data>

To download the stored asset, you can use the value of the url. Attach has a middleware that will render these files for you automatically. By default, the middleware will serve all attachments as long as the user has the UUID of the attachment. If you wish to disable the serving of certain attachments (i.e. secure files that should be authenticated first), you should set the serve option to false.

attachment :passport_scan, :serve => false

Preloading attachments

If you're obtaining an array of objects and wish to have attachment information ready to go, you can include it as follows:

# This will include the details about the attachment (not including the binary)
people = Person.includes_attachments(:cover_photo)
# This will include the details plus the binary
people = Person.includes_attachments(:cover_photo, :_include_binaries => [:_self])

Deleting images

If you wish to remove an image you can simply call destroy. If you want to do this from a form, you can add a checkbox with the name {name}_delete.

Backends

You can choose between storing your images in your database or on the file system. The method you choose will depend on your environment and usage requirements. By default, files are stored in the database.

To use the file system, just use the following:

# Stores files in an 'attachments' directory in the root of your app
Attach.use_filesystem!
# Stores files in the directory you specify
Attach.use_filesystem!(:root => 'path/to/root/dir')

You can also write your own backends. Check out the abstract backend for instructions. Once you've written one, just set it as the backend.

Attach.backend = MyApp::MyCleverAttachBackend.new

Caching & Disposition

When you serve assets out through the included middleware, by default they will be served with a private, max-age=<1 year> cache control header. This can be changed to suit the needs of each type of attachment.

attachment :cover_photo, :cache_type => 'public', :cache_max_age => 5.days

The disposition of a file served by the middleware will be attachment by default. You can change this:

attachment :cover_photo, :disposition => 'inline'

Validation

To validate an image before persisting it to your backend you can include a validation block.

attachment :image do
  validator do |attachment, errors|
    unless Lizard::Image.is_image?(attachment.binary)
      errors << "must be an image"
    end
  end
end

Processing

If processing is required for an uploaded file, this can be acheived by passing a block to the attachment method.

attachment :image do
  processor do |attachment|
    # Do your additional processing on this attachment
    # This might include making thumbnails of an image etc...
  end
end

By default, all processing will happen syncronously which may not be desirable if the processing will take time. To background the processing automatically, you can request the assistance of a worker. You need to use your own worker system to do this, an example is provided below.

# Configure how jobs should be queued
Attach::Processor.background do |attachment|
  ProcessAttachmentJob.queue(:attachment_id => attachment.id)
end

# Define a job (if you don't preload your app, be sure to get the parent initialized before trying to run any processing
# otherwise the processors won't have registered),
class ProcessAttachmentJob < Jobster::Job
  def perform
    if attachment = Attach::Attachment.includes(:parent).find(params['attachment_id'])
      attachment.processor.process
    end
  end
end

Once you have registered a block for queueing (using background), all attachments for the application will be processed in the background.

Sharing Processors

If you have multiple attachments that all need to use the same processor, you can share them.

module ImageProcessing
  # Make a proc that handles your processing
  GetDimensions = proc do |attachment|
    # Do your processing
  end
end

attachment :image do
  # Set it as the processor for each attachment
  processor ImageProcessing::GetDimensions
end

This can also be used for validators.

Custom Data

Attachments have a custom attribute which allows you to store data with an attachment. You might use this to store the width/height of an image in a processor.

attachment :image do
  processor do |attachment|
    image = Lizard::Image.new(attachment.binary)
    attachment.custom['width'] = image.width
    attachment.custom['height'] = image.height
  end
end

Children

Attachments can have child attachments which are associated with the first one. This is useful if you're uploading images and wish to generate different thumbnails for it automatically. It works like this:

Creating children

The easiest place to create children is in the processing block for an attachment. You should call the add_child method with the role for the new item. This should be unique across all children in the parent image. If you upload a new child with the same name later, the original will be removed.

attachment :cover_photo do
  processor do |attachment|
    image = Lizard::Image.new(attachment.binary)
    attachment.add_child(:thumb500) do |c|
      c.binary = image.resize(500, 500).data
      c.file_name = "thumb500x500.jpg"
    end
  end
end

Accessing children

If you have a single object you wish to find a child for, the easiest way is like such...

post = Post.find(31)
post.cover_photo                    # => The original attachment
post.cover_photo.child(:thumb500)   # => The child attachment

If you're loading multiple objects though you may wish to preload the images that you desire in a single query rather than looking up each one in turn.

Post.includes_attachments(:cover_photo => [:thumb500]).each do |post|
  post.cover_photo.child(:thumb500) # => No additional database queries
end

# Include binaries for certain children (in this case thumb500)
posts = Post.includes_attachments(:cover_photo, :_include_binaries => {:cover_photo => [:thumb500]})

CDNs

If you have an origin pull CDN and would like the url attribute for your attachments to include the appropriate CDN host, you can set it.

Attach.asset_host = "https://cdn.exampleapp.com"
You can’t perform that action at this time.